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