aboutsummaryrefslogtreecommitdiff
path: root/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go
diff options
context:
space:
mode:
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go')
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go188
1 files changed, 188 insertions, 0 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go
new file mode 100644
index 000000000..5c21dd0ba
--- /dev/null
+++ b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go
@@ -0,0 +1,188 @@
+// Copyright 2018 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 buffer implements a 2-D buffer of cells.
+package buffer
+
+import (
+ "fmt"
+ "image"
+
+ "github.com/mum4k/termdash/cell"
+ "github.com/mum4k/termdash/private/area"
+ "github.com/mum4k/termdash/private/runewidth"
+)
+
+// NewCells breaks the provided text into cells and applies the options.
+func NewCells(text string, opts ...cell.Option) []*Cell {
+ var res []*Cell
+ for _, r := range text {
+ res = append(res, NewCell(r, opts...))
+ }
+ return res
+}
+
+// Cell represents a single cell on the terminal.
+type Cell struct {
+ // Rune is the rune stored in the cell.
+ Rune rune
+
+ // Opts are the cell options.
+ Opts *cell.Options
+}
+
+// String implements fmt.Stringer.
+func (c *Cell) String() string {
+ return fmt.Sprintf("{%q}", c.Rune)
+}
+
+// NewCell returns a new cell.
+func NewCell(r rune, opts ...cell.Option) *Cell {
+ return &Cell{
+ Rune: r,
+ Opts: cell.NewOptions(opts...),
+ }
+}
+
+// Copy returns a copy the cell.
+func (c *Cell) Copy() *Cell {
+ return &Cell{
+ Rune: c.Rune,
+ Opts: cell.NewOptions(c.Opts),
+ }
+}
+
+// Apply applies the provided options to the cell.
+func (c *Cell) Apply(opts ...cell.Option) {
+ for _, opt := range opts {
+ opt.Set(c.Opts)
+ }
+}
+
+// Buffer is a 2-D buffer of cells.
+// The axes increase right and down.
+// Uninitialized buffer is invalid, use New to create an instance.
+// Don't set cells directly, use the SetCell method instead which safely
+// handles limits and wide unicode characters.
+type Buffer [][]*Cell
+
+// New returns a new Buffer of the provided size.
+func New(size image.Point) (Buffer, error) {
+ if size.X <= 0 {
+ return nil, fmt.Errorf("invalid buffer width (size.X): %d, must be a positive number", size.X)
+ }
+ if size.Y <= 0 {
+ return nil, fmt.Errorf("invalid buffer height (size.Y): %d, must be a positive number", size.Y)
+ }
+
+ b := make([][]*Cell, size.X)
+ for col := range b {
+ b[col] = make([]*Cell, size.Y)
+ for row := range b[col] {
+ b[col][row] = NewCell(0)
+ }
+ }
+ return b, nil
+}
+
+// SetCell sets the rune of the specified cell in the buffer. Returns the
+// number of cells the rune occupies, wide runes can occupy multiple cells when
+// printed on the terminal. See http://www.unicode.org/reports/tr11/.
+// Use the options to specify which attributes to modify, if an attribute
+// option isn't specified, the attribute retains its previous value.
+func (b Buffer) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) {
+ partial, err := b.IsPartial(p)
+ if err != nil {
+ return -1, err
+ }
+ if partial {
+ return -1, fmt.Errorf("cannot set rune %q at point %v, it is a partial cell occupied by a wide rune in the previous cell", r, p)
+ }
+
+ remW, err := b.RemWidth(p)
+ if err != nil {
+ return -1, err
+ }
+ rw := runewidth.RuneWidth(r)
+ if rw == 0 {
+ // Even if the rune is invisible, like the zero-value rune, it still
+ // occupies at least the target cell.
+ rw = 1
+ }
+ if rw > remW {
+ return -1, fmt.Errorf("cannot set rune %q of width %d at point %v, only have %d remaining cells at this line", r, rw, p, remW)
+ }
+
+ c := b[p.X][p.Y]
+ c.Rune = r
+ c.Apply(opts...)
+ return rw, nil
+}
+
+// IsPartial returns true if the cell at the specified point holds a part of a
+// full width rune from a previous cell. See
+// http://www.unicode.org/reports/tr11/.
+func (b Buffer) IsPartial(p image.Point) (bool, error) {
+ size := b.Size()
+ ar, err := area.FromSize(size)
+ if err != nil {
+ return false, err
+ }
+
+ if !p.In(ar) {
+ return false, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
+ }
+
+ if p.X == 0 && p.Y == 0 {
+ return false, nil
+ }
+
+ prevP := image.Point{p.X - 1, p.Y}
+ if prevP.X < 0 {
+ prevP = image.Point{size.X - 1, p.Y - 1}
+ }
+
+ prevR := b[prevP.X][prevP.Y].Rune
+ switch rw := runewidth.RuneWidth(prevR); rw {
+ case 0, 1:
+ return false, nil
+ case 2:
+ return true, nil
+ default:
+ return false, fmt.Errorf("buffer cell %v contains rune %q which has an unsupported rune with %d", prevP, prevR, rw)
+ }
+}
+
+// RemWidth returns the remaining width (horizontal row of cells) available
+// from and inclusive of the specified point.
+func (b Buffer) RemWidth(p image.Point) (int, error) {
+ size := b.Size()
+ ar, err := area.FromSize(size)
+ if err != nil {
+ return -1, err
+ }
+
+ if !p.In(ar) {
+ return -1, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar)
+ }
+ return size.X - p.X, nil
+}
+
+// Size returns the size of the buffer.
+func (b Buffer) Size() image.Point {
+ return image.Point{
+ len(b),
+ len(b[0]),
+ }
+}