package gruid
import "github.com/anaseto/gruid"
Package gruid provides a model for building grid-based applications. The interface abstracts rendering and input for different platforms. There are drivers for terminal apps (gruid-tcell), native graphical apps (gruid-sdl) and browser apps (gruid-js).
The package uses an architecture of updating a model in response to messages strongly inspired from the bubbletea module for building terminal apps (https://github.com/charmbracelet/bubbletea), which in turn is based on the Elm Architecture (https://guide.elm-lang.org/architecture/).
The typical usage looks like this:
// model implements gruid.Model interface and represents the // application's state. type model struct { grid gruid.Grid // user interface grid // other fields with the state of the application } func (m *model) Update(msg gruid.Msg) gruid.Effect { // Update your application's state in response to messages. } func (m *model) Draw() gruid.Grid { // Write your rendering into the grid and return it. } func main() { gd := gruid.NewGrid(80, 24) m := &model{grid: gd, ...} // Specify a driver among the provided ones. driver := tcell.NewDriver(...) app := gruid.NewApp(gruid.AppConfig{ Driver: driver, Model: m, }) // Start the main loop of the application. if err := app.Start(context.Background()); err != nil { log.Fatal(err) } }
The values of type gruid.Effect returned by Update are optional and represent concurrently executed functions that produce messages. The gruid.Grid type is a convenient 2-dimensional slice type representing the screen's logical contents. See the relevant types documentation for details and usage.
Index ¶
- type App
- type AppConfig
- type AttrMask
- type Cell
- type Cmd
- type Color
- type Driver
- type DriverPollMsg
- type Effect
- type Frame
- type FrameCell
- type FrameDecoder
- func NewFrameDecoder(r io.Reader) (*FrameDecoder, error)
- func (fd *FrameDecoder) Decode(framep *Frame) error
- type Grid
- func NewGrid(w, h int) Grid
- func (gd Grid) At(p Point) Cell
- func (gd Grid) Bounds() Range
- func (gd Grid) Contains(p Point) bool
- func (gd Grid) Copy(src Grid) Point
- func (gd Grid) Fill(c Cell)
- func (gd Grid) Iter(fn func(Point, Cell))
- func (gd Grid) Iterator() GridIterator
- func (gd Grid) Map(fn func(Point, Cell) Cell)
- func (gd Grid) Range() Range
- func (gd Grid) Resize(w, h int) Grid
- func (gd Grid) Set(p Point, c Cell)
- func (gd Grid) Size() Point
- func (gd Grid) Slice(rg Range) Grid
- func (gd Grid) String() string
- type GridIterator
- func (it *GridIterator) Cell() Cell
- func (it *GridIterator) Next() bool
- func (it *GridIterator) P() Point
- func (it *GridIterator) Reset()
- func (it *GridIterator) SetCell(c Cell)
- func (it *GridIterator) SetP(p Point)
- type Key
- type ModMask
- type Model
- type MouseAction
- type Msg
- type MsgInit
- type MsgKeyDown
- type MsgMouse
- type MsgQuit
- type MsgScreen
- type Point
- func (p Point) Add(q Point) Point
- func (p Point) Div(k int) Point
- func (p Point) In(rg Range) bool
- func (p Point) Mul(k int) Point
- func (p Point) Shift(x, y int) Point
- func (p Point) String() string
- func (p Point) Sub(q Point) Point
- type Range
- func NewRange(x0, y0, x1, y1 int) Range
- func (rg Range) Add(p Point) Range
- func (rg Range) Column(x int) Range
- func (rg Range) Columns(x0, x1 int) Range
- func (rg Range) Empty() bool
- func (rg Range) Eq(r Range) bool
- func (rg Range) In(r Range) bool
- func (rg Range) Intersect(r Range) Range
- func (rg Range) Iter(fn func(Point))
- func (rg Range) Line(y int) Range
- func (rg Range) Lines(y0, y1 int) Range
- func (rg Range) Overlaps(r Range) bool
- func (rg Range) RelMsg(msg Msg) Msg
- func (rg Range) Shift(x0, y0, x1, y1 int) Range
- func (rg Range) Size() Point
- func (rg Range) String() string
- func (rg Range) Sub(p Point) Range
- func (rg Range) Union(r Range) Range
- type Style
- func (st Style) WithAttrs(attrs AttrMask) Style
- func (st Style) WithBg(cl Color) Style
- func (st Style) WithFg(cl Color) Style
- type Sub
Examples ¶
Types ¶
type App ¶
type App struct { // CatchPanics ensures that Close is called on the driver before ending // the Start loop. When a panic occurs, it will be recovered, the stack // trace will be printed and an error will be returned. It defaults to // true. CatchPanics bool // contains filtered or unexported fields }
App represents a message and model-driven application with a grid-based user interface.
func NewApp ¶
NewApp creates a new App with the given configuration options.
func (*App) Start ¶
Start initializes the application and runs its main loop. The context argument can be used as a means to prematurely cancel the loop. You can usually use an empty context here.
type AppConfig ¶
type AppConfig struct { Model Model // application state Driver Driver // input and rendering driver // FrameWriter is an optional io.Writer for recording frames. They can // be decoded after a successful Start session with a FrameDecoder. If // nil, no frame recording will be done. It is your responsibility to // call Close on the Writer after Start returns. FrameWriter io.Writer // Logger is optional and is used to log non-fatal IO errors. Logger *log.Logger }
AppConfig contains the configuration options for creating a new App.
type AttrMask ¶
type AttrMask uint32
AttrMask can be used to add custom styling information. It can for example be used to map to specific terminal attributes (with GetStyle), or use special images (with GetImage), when appropriate.
It may be used as a bitmask, like terminal attributes, or as a generic value for constants.
const AttrsDefault AttrMask = 0
AttrsDefault represents the default styling attributes.
type Cell ¶
Cell contains all the content and styling information to represent a cell in the grid.
func (Cell) WithRune ¶
WithRune returns a derived Cell with a new Rune.
func (Cell) WithStyle ¶
WithStyle returns a derived Cell with a new Style.
type Cmd ¶
type Cmd func() Msg
Cmd is an Effect that returns a message. Commands returned by Update are executed on their own goroutine. You can use them for things like single event timers and short-lived IO operations with a single result. A nil command is discarded and does nothing.
Cmd implements the Effect interface.
func End ¶
func End() Cmd
End returns a special command that signals the application to end its Start loop. Note that the application does not wait for pending effects to complete before exiting the Start loop, so you may have to wait for any of those commands messages before using End.
type Color ¶
type Color uint32
Color is a generic value for representing colors. Those have to be mapped to concrete foreground and background colors for each driver, as appropriate.
const ColorDefault Color = 0
ColorDefault should get special treatment by drivers and be mapped, when it makes sense, to a default color, both for foreground and background.
type Driver ¶
type Driver interface { // Init initializes the driver, so that you can then call its other // methods. Init() error // PollMsgs is a subscription for input messages. It returns an error // in case the driver input loop suffered a non recoverable error. It // should handle cancellation of the passed context and return as // appropriate. PollMsgs(context.Context, chan<- Msg) error // Flush sends grid's last frame changes to the driver. Flush(Frame) // Close may execute needed code to finalize the screen and release // resources. Redundant Close() calls are ignored. After Close() it is // possible to call Init() again. Close() }
Driver handles both user input and rendering. When creating an App and using the Start main loop, you will not have to call those methods directly. You may reuse the same driver for another application after the current application's Start loop ends.
type DriverPollMsg ¶
type DriverPollMsg interface { // The PollMsg returns an input message if any, in a non-blocking way. // If no message can be retrieved, nil should be returned. If a non // recoverable input error happens, an error can be returned. PollMsg() (Msg, error) }
DriverPollMsg is an optional interface that can be satisfied by drivers. Such drivers will be run such that the message polling is executed in the same thread as main using a non-blocking polling message method, instead of PollMsgs. This may be necessary with drivers whose input system is not thread safe.
type Effect ¶
type Effect interface {
// contains filtered or unexported methods
}
Effect is an interface type for representing either command or subscription functions. Those functions generally represent IO operations, either producing a single message or several. They are executed on their own goroutine after being returned by the Update method of the model. A nil effect is discarded and does nothing.
The types Cmd and Sub implement the Effect interface. See their respective documentation for specific usage details.
func Batch ¶
Batch peforms a bunch of effects concurrently with no ordering guarantees about the potential results.
type Frame ¶
type Frame struct { Time time.Time // time of frame drawing: used for replay Cells []FrameCell // cells that changed from previous frame Width int // width of the whole grid when the frame was issued Height int // height of the whole grid when the frame was issued }
Frame contains the necessary information to draw the frame changes from a frame to the next. One is sent to the driver after every Draw.
type FrameCell ¶
type FrameCell struct { Cell Cell // cell content and styling P Point // absolute position in the whole grid }
FrameCell represents a cell drawing instruction at a specific absolute position in the whole grid.
type FrameDecoder ¶
type FrameDecoder struct {
// contains filtered or unexported fields
}
FrameDecoder manages the decoding of the frame recording stream produced by the running of an application, in case a FrameWriter was provided. It can be used to replay an application session.
func NewFrameDecoder ¶
func NewFrameDecoder(r io.Reader) (*FrameDecoder, error)
NewFrameDecoder returns a FrameDecoder using a given reader as source for frames.
It is your responsibility to call Close on the reader when done.
func (*FrameDecoder) Decode ¶
func (fd *FrameDecoder) Decode(framep *Frame) error
Decode retrieves the next frame from the input stream. The frame pointer should be non nil. If the input is at EOF, it returns the error io.EOF.
type Grid ¶
type Grid struct {
// contains filtered or unexported fields
}
Grid represents the grid that is used to draw a model logical contents that are then sent to the driver. It is a slice type, so it represents a rectangular range within an underlying original grid. Due to how it is represented internally, it is more efficient to iterate whole lines first, as in the following pattern:
max := gd.Size() for y := 0; y < max.Y; y++ { for x := 0; x < max.X; x++ { p := Point{X: x, Y: y} // do something with p and the grid gd } }
Most iterations can be performed using the Slice, Fill, Copy, Map and Iter methods. An alternative choice is to use the Iterator method.
Grid elements must be created with NewGrid.
Code:play
Output:Example¶
package main
import (
"fmt"
"github.com/anaseto/gruid"
)
func main() {
// Create a new 20x20 grid.
gd := gruid.NewGrid(20, 20)
// Fill the whole grid with dots.
gd.Fill(gruid.Cell{Rune: '.'})
// Define a range (5,5)-(15,15).
rg := gruid.NewRange(5, 5, 15, 15)
// Define a slice of the grid using the range.
rectangle := gd.Slice(rg)
// Fill the rectangle with #.
rectangle.Fill(gruid.Cell{Rune: '#'})
// Print the grid using a non-styled string representation.
fmt.Print(gd)
}
....................
....................
....................
....................
....................
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
.....##########.....
....................
....................
....................
....................
....................
func NewGrid ¶
NewGrid returns a new grid with given width and height in cells. The width and height should be positive or null. The new grid contains all positions (X,Y) with 0 <= X < w and 0 <= Y < h. The grid is filled with Cell{Rune: ' '}.
func (Grid) At ¶
At returns the cell content and styling at a given position. If the position is out of range, it returns the zero value.
func (Grid) Bounds ¶
Bounds returns the range that is covered by this grid slice within the underlying original grid.
func (Grid) Contains ¶
Contains returns true if the given relative position is within the grid.
func (Grid) Copy ¶
Copy copies elements from a source grid src into the destination grid gd, and returns the copied grid-slice size, which is the minimum of both grids for each dimension. The result is independent of whether the two grids referenced memory overlaps or not.
func (Grid) Fill ¶
Fill sets the given cell as content for all the grid positions.
func (Grid) Iter ¶
Iter iterates a function on all the grid positions and cells.
func (Grid) Iterator ¶
func (gd Grid) Iterator() GridIterator
Iterator returns an iterator that can be used to iterate on the grid. It may be convenient when more flexibility than the provided by the other iteration functions is needed. It is used as follows:
it := gd.Iterator() for it.Next() { // call it.P() or it.Cell() or it.SetCell() as appropriate }
func (Grid) Map ¶
Map updates the grid content using the given mapping function.
func (Grid) Range ¶
Range returns the range with Min set to (0,0) and Max set to gd.Size(). It may be convenient when using Slice with a range Shift.
func (Grid) Resize ¶
Resize is similar to Slice, but it only specifies new dimensions, and if the range goes beyond the underlying original grid range, it will grow the underlying grid. In case of growth, it preserves the content, and new cells are initialized to Cell{Rune: ' '}.
func (Grid) Set ¶
Set draws cell content and styling at a given position in the grid. If the position is out of range, the function does nothing.
func (Grid) Size ¶
Size returns the grid (width, height) in cells, and is a shorthand for gd.Range().Size().
func (Grid) Slice ¶
Slice returns a rectangular slice of the grid given by a range relative to the grid. If the range is out of bounds of the parent grid, it will be reduced to fit to the available space. The returned grid shares memory with the parent.
This makes it easy to use relative coordinates when working with UI elements.
func (Grid) String ¶
String returns a simplified string representation of the grid's runes, without the styling.
type GridIterator ¶
type GridIterator struct {
// contains filtered or unexported fields
}
GridIterator represents a stateful iterator for a grid. They are created
with the Iterator method.
Code:play
Output:Example¶
package main
import (
"fmt"
"github.com/anaseto/gruid"
)
func main() {
// Create a new 26x2 grid.
gd := gruid.NewGrid(26, 2)
// Get an iterator.
it := gd.Iterator()
// Iterate on the grid and fill it with successive alphabetic
// characters.
r := 'a'
max := gd.Size()
for it.Next() {
it.SetCell(gruid.Cell{Rune: r})
r++
if it.P().X == max.X-1 {
r = 'A'
}
}
// Print the grid using a non-styled string representation.
fmt.Print(gd)
}
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
func (*GridIterator) Cell ¶
func (it *GridIterator) Cell() Cell
Cell returns the Cell in the grid at the iterator's current position.
func (*GridIterator) Next ¶
func (it *GridIterator) Next() bool
Next advances the iterator the next position in the grid.
func (*GridIterator) P ¶
func (it *GridIterator) P() Point
P returns the iterator's current position.
func (*GridIterator) Reset ¶
func (it *GridIterator) Reset()
Reset resets the iterator's state so that it can be used again.
func (*GridIterator) SetCell ¶
func (it *GridIterator) SetCell(c Cell)
SetCell updates the grid cell at the iterator's current position.
func (*GridIterator) SetP ¶
func (it *GridIterator) SetP(p Point)
SetP sets the iterator's current position.
type Key ¶
type Key string
Key represents the name of a key press.
const ( KeyArrowDown Key = "ArrowDown" // can be KP_2 KeyArrowLeft Key = "ArrowLeft" // can be KP_4 KeyArrowRight Key = "ArrowRight" // can be KP_6 KeyArrowUp Key = "ArrowUp" // can be KP_8 KeyBackspace Key = "Backspace" KeyDelete Key = "Delete" KeyEnd Key = "End" // can be KP_1 KeyEnter Key = "Enter" // can be KP_5 (arbitrary choice) KeyEscape Key = "Escape" KeyHome Key = "Home" // can be KP_7 KeyInsert Key = "Insert" KeyPageDown Key = "PageDown" // can be KP_3 KeyPageUp Key = "PageUp" // can be KP_9 KeySpace Key = " " // constant for clarity (single character) KeyTab Key = "Tab" )
This is the list of the supported non single-character named keys. The drivers that support keypad with numlock off may report some KP_* keypad keys as one of this list, as specified in the comments.
func (Key) In ¶
In reports whether the key is found among a given list of keys.
func (Key) IsRune ¶
IsRune reports whether the key is a single-rune string and not a named key.
type ModMask ¶
type ModMask int16
ModMask is a bit mask of modifier keys.
These values represent modifier keys for a MsgKeyDown message. Those are not supported equally well across all platforms and drivers, for both technical and simplicity reasons. In particular, terminal drivers may not report shift for key presses corresponding to upper case letters. Modifiers may conflict in some cases with browser or system shortcuts too. If you want portability across platforms and drivers, your application should not depend on them for its core functionality.
func (ModMask) String ¶
type Model ¶
type Model interface { // Update is called when a message is received. Use it to update your // model in response to messages and/or send commands or subscriptions. // It is always called the first time with a MsgInit message. Update(Msg) Effect // Draw is called after every Update. Use this function to draw the UI // elements in a grid to be returned. If only parts of the grid are to // be updated, you can return a smaller grid slice, or an empty grid // slice to skip any drawing work. Note that the contents of the grid // slice are then compared to the previous state at the same bounds, // and only the changes are sent to the driver anyway. Draw() Grid }
Model contains the application's state.
type MouseAction ¶
type MouseAction int
MouseAction represents mouse buttons.
const ( MouseMain MouseAction = iota // left button MouseAuxiliary // middle button MouseSecondary // right button MouseWheelUp // wheel impulse up MouseWheelDown // wheel impulse down MouseRelease // button release MouseMove // mouse motion )
This is the list of supported mouse buttons and actions. It is intentionally short for simplicity and best portability across drivers. Pressing several mouse buttons simultaneously is not reported and, in those cases, only one release event will be sent.
func (MouseAction) String ¶
func (ma MouseAction) String() string
type Msg ¶
type Msg interface{}
Msg represents an action and triggers the Update function of the model. Note that nil messages are discarded and do not trigger Update.
type MsgInit ¶
type MsgInit struct{}
MsgInit is a special message that is always sent first to Update after calling Start on the application.
type MsgKeyDown ¶
type MsgKeyDown struct { Key Key // name of the key in MsgKeyDown event // Mod represents modifier keys. They are not portable across // different platforms and drivers. Avoid using them for core // functionality in portable applications. Mod ModMask Time time.Time // time when the event was generated }
MsgKeyDown represents a key press.
type MsgMouse ¶
type MsgMouse struct { Action MouseAction // mouse action (click, release, move) P Point // mouse position in the grid Mod ModMask // modifier keys (unequal driver support) Time time.Time // time when the event was generated }
MsgMouse represents a mouse user input event.
type MsgQuit ¶
MsgQuit may be reported by some drivers to request termination of the application, such as when the main window is closed. It reports the time at which the driver's request was received.
type MsgScreen ¶
type MsgScreen struct { Width int // screen width in cells Height int // screen height in cells Time time.Time // time when the event was generated }
MsgScreen is reported by some drivers when the screen has been exposed in some way and a complete redraw is necessary. It may happen for example after a resize, or after a change of tile set invalidating current displayed content. Note that the application takes care of the redraw, so you may not need to handle it in most cases, unless you want to adapt grid size and layout in response to a potential screen resize.
type Point ¶
Point represents an (X,Y) position in a grid. It follows conventions similar to the ones used by the standard library image.Point.
func (Point) Add ¶
Add returns vector p+q.
func (Point) Div ¶
Div returns the vector p/k.
func (Point) In ¶
In reports whether the position is within the given range.
func (Point) Mul ¶
Mul returns the vector p*k.
func (Point) Shift ¶
Shift returns a new point with coordinates shifted by (x,y). It's a shorthand for p.Add(Point{x,y}).
func (Point) String ¶
String returns a string representation of the form "(x,y)".
func (Point) Sub ¶
Sub returns vector p-q.
type Range ¶
type Range struct { Min, Max Point }
Range represents a rectangle in a grid that contains all the positions P such that Min <= P < Max coordinate-wise. A range is well-formed if Min <= Max. When non-empty, Min represents the upper-left position in the range, and Max-(1,1) the lower-right one.
func NewRange ¶
NewRange returns a new Range with coordinates (x0, y0) for Min and (x1, y1) for Max. The returned range will have minimum and maximum coordinates swapped if necessary, so that the range is well-formed.
func (Range) Add ¶
Add returns a range of same size translated by +p.
func (Range) Column ¶
Column reduces the range to relative column x, or an empty range if out of bounds.
func (Range) Columns ¶
Columns reduces the range to relative columns between x0 (included) and x1 (excluded), or an empty range if out of bounds.
func (Range) Empty ¶
Empty reports whether the range contains no positions.
func (Range) Eq ¶
Eq reports whether the two ranges containt the same set of points. All empty ranges are considered equal.
func (Range) In ¶
In reports whether range rg is completely contained in range r.
func (Range) Intersect ¶
Intersect returns the largest range contained both by rg and r. If the two ranges do not overlap, the zero range will be returned.
func (Range) Iter ¶
Iter calls a given function for all the positions of the range.
func (Range) Line ¶
Line reduces the range to relative line y, or an empty range if out of bounds.
func (Range) Lines ¶
Lines reduces the range to relative lines between y0 (included) and y1 (excluded), or an empty range if out of bounds.
func (Range) Overlaps ¶
Overlaps reports whether the two ranges have a non-zero intersection.
func (Range) RelMsg ¶
RelMsg returns a range-relative version of messages defined by the gruid package. Currently, it only affects mouse messages, which are given positions relative to the range.
func (Range) Shift ¶
Shift returns a new range with coordinates shifted by (x0,y0) and (x1,y1).
func (Range) Size ¶
Size returns the (width, height) of the range in cells.
func (Range) String ¶
String returns a string representation of the form "(x0,y0)-(x1,y1)".
func (Range) Sub ¶
Sub returns a range of same size translated by -p.
func (Range) Union ¶
Union returns the smallest range containing both rg and r.
type Style ¶
type Style struct { Fg Color // foreground color Bg Color // background color Attrs AttrMask // custom styling attributes }
Style represents the styling information of a cell: foreground color, background color and custom attributes.
func (Style) WithAttrs ¶
WithAttrs returns a derived style with new attributes.
func (Style) WithBg ¶
WithBg returns a derived style with a new background color.
func (Style) WithFg ¶
WithFg returns a derived style with a new foreground color.
type Sub ¶
Sub is similar to Cmd, but instead of returning a message, it sends messages to a channel. Subscriptions should only be used for long running functions where more than one message will be produced, for example to send messages delivered by a time.Ticker, or to report messages from listening on a socket. The function should handle the context and terminate as appropriate.
Sub implements the Effect interface.
Source Files ¶
grid.go messages.go recording.go ui.go
Directories ¶
Path | Synopsis |
---|---|
paths | Package paths provides utilities for efficient pathfinding in rectangular maps. |
rl | Package rl provides some facilities for common roguelike programming needs: event queue, field of view and map generation. |
tiles | Package tiles provides common utilities for manipulation of graphical tiles, such as drawing fonts. |
ui | Package ui defines common UI utilities for gruid: menu/table widget, pager, text input, label, text drawing facilities and replay functionality. |
- Version
- v0.22.0 (latest)
- Published
- Oct 23, 2022
- Platform
- linux/amd64
- Imports
- 11 packages
- Last checked
- 8 hours ago –
Tools for package owners.