1// Copyright 2019 Graham Clark. All rights reserved.  Use of this source
2// code is governed by the MIT license that can be found in the LICENSE
3// file.
4
5package gowid
6
7import (
8	"fmt"
9	"io"
10	"strings"
11	"unicode/utf8"
12
13	"github.com/gcla/gowid/gwutil"
14	"github.com/gdamore/tcell"
15	"github.com/mattn/go-runewidth"
16	"github.com/pkg/errors"
17)
18
19//======================================================================
20
21// ICanvasLineReader can provide a particular line of Cells, at the specified y
22// offset. The result may or may not be a copy of the actual Cells, and is determined
23// by whether the user requested a copy and/or the capability of the ICanvasLineReader
24// (maybe it has to provide a copy).
25type ICanvasLineReader interface {
26	Line(int, LineCopy) LineResult
27}
28
29// ICanvasMarkIterator will call the supplied function argument with the name and
30// position of every mark set on the canvas. If the function returns true, the
31// loop is terminated early.
32type ICanvasMarkIterator interface {
33	RangeOverMarks(f func(key string, value CanvasPos) bool)
34}
35
36// ICanvasCellReader can provide a Cell given a row and a column.
37type ICanvasCellReader interface {
38	CellAt(col, row int) Cell
39}
40
41type IAppendCanvas interface {
42	IRenderBox
43	ICanvasLineReader
44	ICanvasMarkIterator
45}
46
47type IMergeCanvas interface {
48	IRenderBox
49	ICanvasCellReader
50	ICanvasMarkIterator
51}
52
53type IDrawCanvas interface {
54	IRenderBox
55	ICanvasLineReader
56	CursorEnabled() bool
57	CursorCoords() CanvasPos
58}
59
60// ICanvas is the interface of any object which can generate a 2-dimensional
61// array of Cells that are intended to be rendered on a terminal. This interface is
62// pretty awful - cluttered and inconsistent and subject to cleanup... Note though
63// that this interface is not here as the minimum requirement for providing arguments
64// to a function or module - instead it's supposed to be an API surface for widgets
65// so includes features that I am trying to guess will be needed, or that widgets
66// already need.
67type ICanvas interface {
68	Duplicate() ICanvas
69	MergeUnder(c IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool)
70	AppendBelow(c IAppendCanvas, doCursor bool, makeCopy bool)
71	AppendRight(c IMergeCanvas, useCursor bool)
72	SetCellAt(col, row int, c Cell)
73	SetLineAt(row int, line []Cell)
74	Truncate(above, below int)
75	ExtendRight(cells []Cell)
76	ExtendLeft(cells []Cell)
77	TrimRight(cols int)
78	TrimLeft(cols int)
79	SetCursorCoords(col, row int)
80	SetMark(name string, col, row int)
81	GetMark(name string) (CanvasPos, bool)
82	RemoveMark(name string)
83	ICanvasMarkIterator
84	ICanvasCellReader
85	IDrawCanvas
86	fmt.Stringer
87}
88
89// LineResult is returned by some Canvas Line-accessing APIs. If the Canvas
90// can return a line without copying it, the Copied field will be false, and
91// the caller is expected to make a copy if necessary (or risk modifying the
92// original).
93type LineResult struct {
94	Line   []Cell
95	Copied bool
96}
97
98// LineCopy is an argument provided to some Canvas APIs, like Line(). It tells
99// the function how to allocate the backing array for a line if the line it
100// returns must be a copy. Typically the API will return a type that indicates
101// whether the result is a copy or not. Since the caller may receive a copy,
102// it can help to indicate the allocation details like length and capacity in
103// case the caller intends to extend the line returned for some other use.
104type LineCopy struct {
105	Len int
106	Cap int
107}
108
109//======================================================================
110
111// LineCanvas exists to make an array of Cells conform to some interfaces, specifically
112// IRenderBox (it has a width of len(.) and a height of 1), IAppendCanvas, to allow
113// an array of Cells to be passed to the canvas function AppendLine(), and ICanvasLineReader
114// so that an array of Cells can act as a line returned from a canvas.
115type LineCanvas []Cell
116
117// BoxColumns lets LineCanvas conform to IRenderBox
118func (c LineCanvas) BoxColumns() int {
119	return len(c)
120}
121
122// BoxRows lets LineCanvas conform to IRenderBox
123func (c LineCanvas) BoxRows() int {
124	return 1
125}
126
127// BoxRows lets LineCanvas conform to IWidgetDimension
128func (c LineCanvas) ImplementsWidgetDimension() {}
129
130// Line lets LineCanvas conform to ICanvasLineReader
131func (c LineCanvas) Line(y int, cp LineCopy) LineResult {
132	return LineResult{
133		Line:   c,
134		Copied: false,
135	}
136}
137
138// RangeOverMarks lets LineCanvas conform to ICanvasMarkIterator
139func (c LineCanvas) RangeOverMarks(f func(key string, value CanvasPos) bool) {}
140
141var _ IAppendCanvas = (*LineCanvas)(nil)
142var _ ICanvasLineReader = (*LineCanvas)(nil)
143var _ ICanvasMarkIterator = (*LineCanvas)(nil)
144
145//======================================================================
146
147var emptyLine [4096]Cell
148
149type EmptyLineTooLong struct {
150	Requested int
151}
152
153var _ error = EmptyLineTooLong{}
154
155func (e EmptyLineTooLong) Error() string {
156	return fmt.Sprintf("Tried to make an empty line too long - tried %d, max is %d", e.Requested, len(emptyLine))
157}
158
159// EmptyLine provides a ready-allocated source of empty cells. Of course this is to be
160// treated as read-only.
161func EmptyLine(length int) []Cell {
162	if length < 0 {
163		length = 0
164	}
165	if length > len(emptyLine) {
166		panic(errors.WithStack(EmptyLineTooLong{Requested: length}))
167	}
168	return emptyLine[0:length]
169}
170
171// CanvasPos is a convenience struct to represent the coordinates of a position on a canvas.
172type CanvasPos struct {
173	X, Y int
174}
175
176func (c CanvasPos) PlusX(n int) CanvasPos {
177	return CanvasPos{X: c.X + n, Y: c.Y}
178}
179
180func (c CanvasPos) PlusY(n int) CanvasPos {
181	return CanvasPos{X: c.X, Y: c.Y + n}
182}
183
184//======================================================================
185
186type CanvasSizeWrong struct {
187	Requested IRenderSize
188	Actual    IRenderBox
189}
190
191var _ error = CanvasSizeWrong{}
192
193func (e CanvasSizeWrong) Error() string {
194	return fmt.Sprintf("Canvas size %v, %v does not match render size %v", e.Actual.BoxColumns(), e.Actual.BoxRows(), e.Requested)
195}
196
197// PanicIfCanvasNotRightSize is for debugging - it panics if the size of the supplied canvas does
198// not conform to the size specified by the size argument. For a box argument, columns and rows are
199// checked; for a flow argument, columns are checked.
200func PanicIfCanvasNotRightSize(c IRenderBox, size IRenderSize) {
201	switch sz := size.(type) {
202	case IRenderBox:
203		if (c.BoxColumns() != sz.BoxColumns() && c.BoxRows() > 0) || c.BoxRows() != sz.BoxRows() {
204			panic(errors.WithStack(CanvasSizeWrong{Requested: size, Actual: c}))
205		}
206	case IRenderFlowWith:
207		if c.BoxColumns() != sz.FlowColumns() {
208			panic(errors.WithStack(CanvasSizeWrong{Requested: size, Actual: c}))
209		}
210	}
211}
212
213type IRightSizeCanvas interface {
214	IRenderBox
215	ExtendRight(cells []Cell)
216	TrimRight(cols int)
217	Truncate(above, below int)
218	AppendBelow(c IAppendCanvas, doCursor bool, makeCopy bool)
219}
220
221func MakeCanvasRightSize(c IRightSizeCanvas, size IRenderSize) {
222	switch sz := size.(type) {
223	case IRenderBox:
224		rightSizeCanvas(c, sz.BoxColumns(), sz.BoxRows())
225	case IRenderFlowWith:
226		rightSizeCanvasHorizontally(c, sz.FlowColumns())
227	}
228}
229
230func rightSizeCanvas(c IRightSizeCanvas, cols int, rows int) {
231	rightSizeCanvasHorizontally(c, cols)
232	rightSizeCanvasVertically(c, rows)
233}
234
235func rightSizeCanvasHorizontally(c IRightSizeCanvas, cols int) {
236	if c.BoxColumns() > cols {
237		c.TrimRight(cols)
238	} else if c.BoxColumns() < cols {
239		c.ExtendRight(EmptyLine(cols - c.BoxColumns()))
240	}
241}
242
243func rightSizeCanvasVertically(c IRightSizeCanvas, rows int) {
244	if c.BoxRows() > rows {
245		c.Truncate(0, c.BoxRows()-rows)
246	} else if c.BoxRows() < rows {
247		AppendBlankLines(c, rows-c.BoxRows())
248	}
249}
250
251//======================================================================
252
253// Canvas is a simple implementation of ICanvas, and is returned by the Render() function
254// of all the current widgets. It represents the canvas by a 2-dimensional array of Cells -
255// no tricks or attempts to optimize this yet! The canvas also stores a map of string
256// identifiers to positions - for example, the cursor position is tracked this way, and the
257// menu widget keeps track of where it should render a "dropdown" using canvas marks. Most
258// Canvas APIs expect that each line has the same length.
259type Canvas struct {
260	Lines  [][]Cell // inner array is a line
261	Marks  *map[string]CanvasPos
262	maxCol int
263}
264
265// NewCanvas returns an initialized Canvas struct. Its size is 0 columns and
266// 0 rows.
267func NewCanvas() *Canvas {
268	lines := make([][]Cell, 0, 120)
269	res := &Canvas{
270		Lines: lines[:0],
271	}
272	var _ io.Writer = res
273	return res
274}
275
276// NewCanvasWithLines allocates a canvas struct and sets its contents to the
277// 2-d array provided as an argument.
278func NewCanvasWithLines(lines [][]Cell) *Canvas {
279	c := &Canvas{
280		Lines: lines,
281	}
282	c.AlignRight()
283	c.maxCol = c.ComputeCurrentMaxColumn()
284	var _ io.Writer = c
285	return c
286}
287
288// NewCanvasOfSize returns a canvas struct of size cols x rows, where
289// each Cell is default-initialized (i.e. empty).
290func NewCanvasOfSize(cols, rows int) *Canvas {
291	return NewCanvasOfSizeExt(cols, rows, Cell{})
292}
293
294// NewCanvasOfSize returns a canvas struct of size cols x rows, where
295// each Cell is initialized by copying the fill argument.
296func NewCanvasOfSizeExt(cols, rows int, fill Cell) *Canvas {
297	fillArr := make([]Cell, cols)
298	for i := 0; i < cols; i++ {
299		fillArr[i] = fill
300	}
301
302	res := NewCanvas()
303	if rows > 0 {
304		res.Lines = append(res.Lines, fillArr)
305		for i := 0; i < rows-1; i++ {
306			res.Lines = append(res.Lines, make([]Cell, 0, 120))
307		}
308	}
309	res.AlignRightWith(fill)
310	res.maxCol = res.ComputeCurrentMaxColumn()
311
312	var _ io.Writer = res
313
314	return res
315}
316
317// Duplicate returns a deep copy of the receiver canvas.
318func (c *Canvas) Duplicate() ICanvas {
319	res := NewCanvasOfSize(c.BoxColumns(), c.BoxRows())
320	for i := 0; i < c.BoxRows(); i++ {
321		copy(res.Lines[i], c.Lines[i])
322	}
323	if c.Marks != nil {
324		marks := make(map[string]CanvasPos)
325		res.Marks = &marks
326		for k, v := range *c.Marks {
327			(*res.Marks)[k] = v
328		}
329	}
330	return res
331}
332
333type IRangeOverCanvas interface {
334	IRenderBox
335	ICanvasCellReader
336	SetCellAt(col, row int, c Cell)
337}
338
339// RangeOverCanvas applies the supplied function to each cell,
340// modifying it in place.
341func RangeOverCanvas(c IRangeOverCanvas, f ICellProcessor) {
342	for i := 0; i < c.BoxRows(); i++ {
343		for j := 0; j < c.BoxColumns(); j++ {
344			c.SetCellAt(j, i, f.ProcessCell(c.CellAt(j, i)))
345		}
346	}
347}
348
349// Line provides access to the lines of the canvas. LineCopy
350// determines what the Line() function should allocate if it
351// needs to make a copy of the Line. Return true if line was
352// copied.
353func (c *Canvas) Line(y int, cp LineCopy) LineResult {
354	return LineResult{
355		Line:   c.Lines[y],
356		Copied: false,
357	}
358}
359
360// BoxColumns helps Canvas conform to IRenderBox.
361func (c *Canvas) BoxColumns() int {
362	return c.maxCol
363}
364
365// BoxRows helps Canvas conform to IRenderBox.
366func (c *Canvas) BoxRows() int {
367	return len(c.Lines)
368}
369
370// BoxRows helps Canvas conform to IWidgetDimension.
371func (c *Canvas) ImplementsWidgetDimension() {}
372
373// ComputeCurrentMaxColumn walks the 2-d array of Cells to determine
374// the length of the longest line. This is used by certain APIs that
375// manipulate the canvas.
376func (c *Canvas) ComputeCurrentMaxColumn() int {
377	res := 0
378	for _, line := range c.Lines {
379		res = gwutil.Max(res, len(line))
380	}
381	return res
382}
383
384// Write lets Canvas conform to io.Writer. Since each Canvas Cell holds a
385// rune, the byte array argument is interpreted as the UTF-8 encoding of
386// a sequence of runes.
387func (c *Canvas) Write(p []byte) (n int, err error) {
388	return WriteToCanvas(c, p)
389}
390
391// WriteToCanvas extracts the logic of implementing io.Writer into a free
392// function that can be used by any canvas implementing ICanvas.
393func WriteToCanvas(c IRangeOverCanvas, p []byte) (n int, err error) {
394	done := 0
395	maxcol := c.BoxColumns()
396	line := 0
397	col := 0
398	for i, chr := range string(p) {
399		if c.BoxRows() > line {
400			switch chr {
401			case '\n':
402				for col < maxcol {
403					c.SetCellAt(col, line, Cell{})
404					col++
405				}
406				line++
407				col = 0
408			default:
409				wid := runewidth.RuneWidth(chr)
410				if col+wid > maxcol {
411					col = 0
412					line++
413				}
414				c.SetCellAt(col, line, c.CellAt(col, line).WithRune(chr))
415				col += wid
416			}
417			done = i + utf8.RuneLen(chr)
418		} else {
419			break
420		}
421	}
422	return done, nil
423}
424
425// CursorEnabled returns true if the cursor is enabled in this canvas, false otherwise.
426func (c *Canvas) CursorEnabled() bool {
427	ok := false
428	if c.Marks != nil {
429		_, ok = (*c.Marks)["cursor"]
430	}
431	return ok
432}
433
434// CursorCoords returns a pair of ints representing the current cursor coordinates. Note
435// that the caller must be sure the Canvas's cursor is enabled.
436func (c *Canvas) CursorCoords() CanvasPos {
437	var pos CanvasPos
438	ok := false
439	if c.Marks != nil {
440		pos, ok = (*c.Marks)["cursor"]
441	}
442	if !ok {
443		// Caller must check first
444		panic(errors.New("Cursor is off!"))
445	}
446	return pos
447}
448
449// SetCursorCoords will set the Canvas's cursor coordinates. The special input of (-1,-1)
450// will disable the cursor.
451func (c *Canvas) SetCursorCoords(x, y int) {
452	if x == -1 && y == -1 {
453		c.RemoveMark("cursor")
454	} else {
455		c.SetMark("cursor", x, y)
456	}
457}
458
459// SetMark allows the caller to store a string identifier at a particular position in the
460// Canvas. The menu widget uses this feature to keep track of where it should "open", acting
461// as an overlay over the widgets below.
462func (c *Canvas) SetMark(name string, x, y int) {
463	if c.Marks == nil {
464		marks := make(map[string]CanvasPos)
465		c.Marks = &marks
466	}
467	(*c.Marks)[name] = CanvasPos{X: x, Y: y}
468}
469
470// GetMark returns the position and presence/absence of the specified string identifier
471// in the Canvas.
472func (c *Canvas) GetMark(name string) (CanvasPos, bool) {
473	ok := false
474	var i CanvasPos
475	if c.Marks != nil {
476		i, ok = (*c.Marks)[name]
477	}
478	return i, ok
479}
480
481// RemoveMark removes a mark from the Canvas.
482func (c *Canvas) RemoveMark(name string) {
483	if c.Marks != nil {
484		delete(*c.Marks, name)
485	}
486}
487
488// RangeOverMarks applies the supplied function to each mark and position in the
489// received Canvas. If the function returns false, the loop is terminated.
490func (c *Canvas) RangeOverMarks(f func(key string, value CanvasPos) bool) {
491	if c.Marks != nil {
492		for k, v := range *c.Marks {
493			if !f(k, v) {
494				break
495			}
496		}
497	}
498}
499
500// CellAt returns the Cell at the Canvas position provided. Note that the
501// function assumes the caller has ensured the position is not out of
502// bounds.
503func (c *Canvas) CellAt(col, row int) Cell {
504	return c.Lines[row][col]
505}
506
507// SetCellAt sets the Canvas Cell at the position provided. Note that the
508// function assumes the caller has ensured the position is not out of
509// bounds.
510func (c *Canvas) SetCellAt(col, row int, cell Cell) {
511	c.Lines[row][col] = cell
512}
513
514// SetLineAt sets a line of the Canvas at the given y position. The function
515// assumes a line of the correct width has been provided.
516func (c *Canvas) SetLineAt(row int, line []Cell) {
517	c.Lines[row] = line
518}
519
520// AppendLine will append the array of Cells provided to the bottom of
521// the receiver Canvas. If the makeCopy argument is true, a copy is made
522// of the provided Cell array; otherwise, a slice is taken and used
523// directly, meaning the Canvas will hold a reference to the underlying
524// array.
525func (c *Canvas) AppendLine(line []Cell, makeCopy bool) {
526	newwidth := gwutil.Max(c.BoxColumns(), len(line))
527	var newline []Cell
528	if cap(line) < newwidth {
529		makeCopy = true
530	}
531	if makeCopy {
532		newline = make([]Cell, newwidth)
533		copy(newline, line)
534	} else if len(line) < newwidth {
535		// extend slice
536		newline = line[0:newwidth]
537	} else {
538		newline = line
539	}
540	c.Lines = append(c.Lines, newline)
541	c.AlignRight()
542}
543
544// String lets Canvas conform to fmt.Stringer.
545func (c *Canvas) String() string {
546	return CanvasToString(c)
547}
548
549func CanvasToString(c ICanvas) string {
550	lineStrings := make([]string, c.BoxRows())
551	for i := 0; i < c.BoxRows(); i++ {
552		line := c.Line(i, LineCopy{}).Line
553		curLine := make([]rune, 0)
554		for x := 0; x < len(line); {
555			r := line[x].Rune()
556			curLine = append(curLine, r)
557			x += runewidth.RuneWidth(r)
558		}
559		lineStrings[i] = string(curLine)
560	}
561	return strings.Join(lineStrings, "\n")
562}
563
564// ExtendRight appends to each line of the receiver Canvas the array of
565// Cells provided as an argument.
566func (c *Canvas) ExtendRight(cells []Cell) {
567	if len(cells) > 0 {
568		for i := 0; i < len(c.Lines); i++ {
569			if len(c.Lines[i])+len(cells) > cap(c.Lines[i]) {
570				widerLine := make([]Cell, len(c.Lines[i]), len(c.Lines[i])+len(cells))
571				copy(widerLine, c.Lines[i])
572				c.Lines[i] = widerLine
573			}
574			c.Lines[i] = append(c.Lines[i], cells...)
575		}
576		c.maxCol += len(cells)
577	}
578}
579
580// ExtendLeft prepends to each line of the receiver Canvas the array of
581// Cells provided as an argument.
582func (c *Canvas) ExtendLeft(cells []Cell) {
583	if len(cells) > 0 {
584		for i := 0; i < len(c.Lines); i++ {
585			cellsCopy := make([]Cell, len(cells)+len(c.Lines[i]))
586			copy(cellsCopy, cells)
587			copy(cellsCopy[len(cells):], c.Lines[i])
588			c.Lines[i] = cellsCopy
589		}
590		if c.Marks != nil {
591			for k, pos := range *c.Marks {
592				(*c.Marks)[k] = pos.PlusX(len(cells))
593			}
594		}
595		c.maxCol += len(cells)
596	}
597}
598
599// AppendBelow appends the supplied Canvas to the "bottom" of the receiver Canvas. If
600// doCursor is true and the supplied Canvas has an enabled cursor, it is applied to
601// the received Canvas, with a suitable Y offset. If makeCopy is true then the supplied
602// Canvas is copied; if false, and the supplied Canvas is capable of giving up
603// ownership of its data structures, then they are moved to the receiver Canvas.
604func (c *Canvas) AppendBelow(c2 IAppendCanvas, doCursor bool, makeCopy bool) {
605	cw := c.BoxColumns()
606	lenc := len(c.Lines)
607	for i := 0; i < c2.BoxRows(); i++ {
608		lr := c2.Line(i, LineCopy{
609			Len: cw,
610			Cap: cw,
611		})
612		if makeCopy && !lr.Copied {
613			line := make([]Cell, cw)
614			copy(line, lr.Line)
615			c.Lines = append(c.Lines, line)
616		} else {
617			c.Lines = append(c.Lines, lr.Line)
618		}
619	}
620	c.AlignRight()
621	c2.RangeOverMarks(func(k string, pos CanvasPos) bool {
622		if doCursor || (k != "cursor") {
623			if c.Marks == nil {
624				marks := make(map[string]CanvasPos)
625				c.Marks = &marks
626			}
627			(*c.Marks)[k] = pos.PlusY(lenc)
628		}
629		return true
630	})
631}
632
633// Truncate removes "above" lines from above the receiver Canvas, and
634// "below" lines from below.
635func (c *Canvas) Truncate(above, below int) {
636	if above < 0 {
637		panic(errors.New("Lines to cut above must be >= 0"))
638	}
639	if below < 0 {
640		panic(errors.New("Lines to cut below must be >= 0"))
641	}
642	cutAbove := gwutil.Min(len(c.Lines), above)
643	c.Lines = c.Lines[cutAbove:]
644	cutBelow := len(c.Lines) - gwutil.Min(len(c.Lines), below)
645	c.Lines = c.Lines[:cutBelow]
646	if c.Marks != nil {
647		for k, pos := range *c.Marks {
648			(*c.Marks)[k] = pos.PlusY(-cutAbove)
649		}
650	}
651}
652
653type CellMergeFunc func(lower, upper Cell) Cell
654
655// MergeWithFunc merges the supplied Canvas with the receiver canvas, where the receiver canvas
656// is considered to start at column leftOffset and at row topOffset, therefore translated some
657// distance from the top-left, and the receiver Canvas is the one modified. A function argument
658// is supplied which specifies how Cells are merged, one by one e.g. which style takes effect,
659// which rune, and so on.
660func (c *Canvas) MergeWithFunc(c2 IMergeCanvas, leftOffset, topOffset int, fn CellMergeFunc, bottomGetsCursor bool) {
661	c2w := c2.BoxColumns()
662	for i := 0; i < c2.BoxRows(); i++ {
663		if i+topOffset < len(c.Lines) {
664			cl := len(c.Lines[i+topOffset])
665			for j := 0; j < c2w; j++ {
666				if j+leftOffset < cl {
667					c2ij := c2.CellAt(j, i)
668					c.Lines[i+topOffset][j+leftOffset] = fn(c.Lines[i+topOffset][j+leftOffset], c2ij)
669				} else {
670					break
671				}
672			}
673		}
674	}
675	c2.RangeOverMarks(func(k string, v CanvasPos) bool {
676		// Special treatment for the cursor mark - to allow widgets to display the cursor via
677		// a "lower" widget. The terminal will typically support displaying one cursor only.
678		if k != "cursor" || !bottomGetsCursor {
679			if c.Marks == nil {
680				marks := make(map[string]CanvasPos)
681				c.Marks = &marks
682			}
683			(*c.Marks)[k] = v.PlusX(leftOffset).PlusY(topOffset)
684		}
685		return true
686	})
687}
688
689// MergeUnder merges the supplied Canvas "under" the receiver Canvas, meaning the
690// receiver Canvas's Cells' settings are given priority.
691func (c *Canvas) MergeUnder(c2 IMergeCanvas, leftOffset, topOffset int, bottomGetsCursor bool) {
692	c.MergeWithFunc(c2, leftOffset, topOffset, Cell.MergeUnder, bottomGetsCursor)
693}
694
695// AppendRight appends the supplied Canvas to the right of the receiver Canvas. It
696// assumes both Canvases have the same number of rows. If useCursor is true and the
697// supplied Canvas has an enabled cursor, then it is applied with a suitable X
698// offset applied.
699func (c *Canvas) AppendRight(c2 IMergeCanvas, useCursor bool) {
700	m := c.BoxColumns()
701	c2w := c2.BoxColumns()
702	for y := 0; y < c2.BoxRows(); y++ {
703		if cap(c.Lines[y]) < len(c.Lines[y])+c2w {
704			widerLine := make([]Cell, len(c.Lines[y])+c2w)
705			copy(widerLine, c.Lines[y])
706			c.Lines[y] = widerLine
707		} else {
708			c.Lines[y] = c.Lines[y][0 : len(c.Lines[y])+c2w]
709		}
710		for x := 0; x < c2w; x++ {
711			c.Lines[y][x+m] = c2.CellAt(x, y)
712		}
713	}
714
715	c2.RangeOverMarks(func(k string, v CanvasPos) bool {
716		if (k != "cursor") || useCursor {
717			if c.Marks == nil {
718				marks := make(map[string]CanvasPos)
719				c.Marks = &marks
720			}
721			(*c.Marks)[k] = v.PlusX(m)
722		}
723		return true
724	})
725	c.maxCol = m + c2w
726}
727
728// TrimRight removes columns from the right of the receiver Canvas until there
729// is the specified number left.
730func (c *Canvas) TrimRight(colsToHave int) {
731	for i := 0; i < len(c.Lines); i++ {
732		if len(c.Lines[i]) > colsToHave {
733			c.Lines[i] = c.Lines[i][0:colsToHave]
734		}
735	}
736	c.maxCol = colsToHave
737}
738
739// TrimLeft removes columns from the left of the receiver Canvas until there
740// is the specified number left.
741func (c *Canvas) TrimLeft(colsToHave int) {
742	colsToTrim := 0
743	for i := 0; i < len(c.Lines); i++ {
744		colsToTrim = gwutil.Max(colsToTrim, len(c.Lines[i])-colsToHave)
745	}
746	for i := 0; i < len(c.Lines); i++ {
747		if len(c.Lines[i]) >= colsToTrim {
748			c.Lines[i] = c.Lines[i][colsToTrim:]
749		}
750	}
751	if c.Marks != nil {
752		for k, v := range *c.Marks {
753			(*c.Marks)[k] = v.PlusX(-colsToTrim)
754		}
755	}
756}
757
758func appendCell(slice []Cell, data Cell, num int) []Cell {
759	m := len(slice)
760	n := m + num
761	if n > cap(slice) { // if necessary, reallocate
762		newSlice := make([]Cell, (n+1)*2)
763		copy(newSlice, slice)
764		slice = newSlice
765	}
766	slice = slice[0:n]
767	for i := 0; i < num; i++ {
768		slice[m+i] = data
769	}
770	return slice
771}
772
773// AlignRightWith will extend each row of Cells in the receiver Canvas with
774// the supplied Cell in order to ensure all rows are the same length. Note
775// that the Canvas will not increase in width as a result.
776func (c *Canvas) AlignRightWith(cell Cell) {
777	m := c.ComputeCurrentMaxColumn()
778	for j, line := range c.Lines {
779		lineLen := len(line)
780		cols := m - lineLen
781		if len(c.Lines[j])+cols > cap(c.Lines[j]) {
782			tmp := make([]Cell, len(c.Lines[j]), len(c.Lines[j])+cols+32)
783			copy(tmp, c.Lines[j])
784			c.Lines[j] = tmp[0:len(c.Lines[j])]
785		}
786		c.Lines[j] = appendCell(c.Lines[j], cell, m-lineLen)
787	}
788	c.maxCol = m
789}
790
791// AlignRight will extend each row of Cells in the receiver Canvas with an
792// empty Cell in order to ensure all rows are the same length. Note that
793// the Canvas will not increase in width as a result.
794func (c *Canvas) AlignRight() {
795	c.AlignRightWith(Cell{})
796}
797
798// Draw will render a Canvas to a tcell Screen.
799func Draw(canvas IDrawCanvas, mode IColorMode, screen tcell.Screen) {
800	cpos := CanvasPos{X: -1, Y: -1}
801	if canvas.CursorEnabled() {
802		cpos = canvas.CursorCoords()
803	}
804
805	screen.ShowCursor(-1, -1)
806
807	for y := 0; y < canvas.BoxRows(); y++ {
808		line := canvas.Line(y, LineCopy{})
809		vline := line.Line
810		for x := 0; x < len(vline); {
811			c := vline[x]
812			f, b, s := c.ForegroundColor(), c.BackgroundColor(), c.Style()
813			st := MakeCellStyle(f, b, s)
814			screen.SetContent(x, y, c.Rune(), nil, st)
815			x += runewidth.RuneWidth(c.Rune())
816
817			if x == cpos.X && y == cpos.Y {
818				screen.ShowCursor(x, y)
819			}
820		}
821	}
822}
823
824//======================================================================
825// Local Variables:
826// mode: Go
827// fill-column: 110
828// End:
829