1# You may distribute under the terms of either the GNU General Public License 2# or the Artistic License (the same terms as Perl itself) 3# 4# (C) Paul Evans, 2013-2017 -- leonerd@leonerd.org.uk 5 6package Tickit::RenderBuffer 0.72; 7 8use v5.14; 9use warnings; 10 11use Carp; 12use Scalar::Util qw( refaddr ); 13 14# Load the XS code 15require Tickit; 16 17use Tickit::Utils qw( textwidth ); 18use Tickit::Rect; 19use Tickit::Pen 0.31; 20 21use Struct::Dumb qw( readonly_struct ); 22 23# Exported API constants 24use Exporter 'import'; 25our @EXPORT_OK = qw( 26 LINE_SINGLE LINE_DOUBLE LINE_THICK 27 CAP_START CAP_END CAP_BOTH 28); 29use constant { 30 LINE_SINGLE => 0x01, 31 LINE_DOUBLE => 0x02, 32 LINE_THICK => 0x03, 33}; 34use constant { 35 CAP_START => 0x01, 36 CAP_END => 0x02, 37 CAP_BOTH => 0x03, 38}; 39 40 41=head1 NAME 42 43C<Tickit::RenderBuffer> - efficiently render text and line-drawing 44 45=head1 SYNOPSIS 46 47 package Tickit::Widget::Something; 48 ... 49 50 sub render_to_rb 51 { 52 my $self = shift; 53 my ( $rb, $rect ) = @_; 54 55 $rb->eraserect( $rect ); 56 $rb->text_at( 2, 2, "Hello, world!", $self->pen ); 57 } 58 59Z<> 60 61 $win->set_on_expose( sub { 62 my ( $win, $rb, $rect ) = @_; 63 64 $rb->eraserect( $rect ); 65 $rb->text_at( 2, 2, "Hello, world!" ); 66 }); 67 68=head1 DESCRIPTION 69 70Provides a buffer of pending rendering operations to apply to the terminal. 71The buffer is modified by rendering operations performed by widgets or other 72code, and flushed to the terminal when complete. 73 74This provides the following advantages: 75 76=over 2 77 78=item * 79 80Changes can be made in any order, and will be flushed in top-to-bottom, 81left-to-right order, minimising cursor movements. 82 83=item * 84 85Buffered content can be overwritten or partly erased once stored, simplifying 86some styles of drawing operation. Large areas can be erased, and then redrawn 87with text or lines, without causing a double-drawing flicker on the output 88terminal. 89 90=item * 91 92The buffer supports line-drawing, complete with merging of line segments that 93meet in a character cell. Boxes, grids, and other shapes can be easily formed 94by drawing separate line segments, and the C<RenderBuffer> will handle the 95corners and other junctions formed. 96 97=item * 98 99A single buffer can be passed around all of the windows or widgets to properly 100combine line segments and layering effects, making it possible to create many 101kinds of sub-divided or layered output. 102 103=back 104 105Drawing methods come in two forms; absolute, and cursor-relative: 106 107=over 2 108 109=item * 110 111Absolute methods, identified by their name having a suffixed C<_at>, operate 112on a position within the buffer specified by their argument. 113 114=item * 115 116Cursor-relative methods, identified by their lack of C<_at> suffix, operate at 117and update the position of the "virtual cursor". This is a position within the 118buffer that can be set using the C<goto> method. The position of the virtual 119cursor is not affected by the absolute-position methods. 120 121=back 122 123=head2 State Stack 124 125The C<RenderBuffer> stores a stack of saved state. The state of the buffer can 126be stored using the C<save> method, so that changes can be made, before 127finally restoring back to that state using C<restore>. The following items of 128state are saved: 129 130=over 2 131 132=item * 133 134The virtual cursor position 135 136=item * 137 138The clipping rectangle 139 140=item * 141 142The render pen 143 144=item * 145 146The translation offset 147 148=item * 149 150The set of masked regions 151 152=back 153 154When the state is saved to the stack, the render pen is remembered and merged 155with any pen set using the C<setpen> method. 156 157The queued content to render is not part of the state stack. It is intended 158that the state stack be used to implement recursive delegation of drawing 159operations down a tree of code, allowing child contexts to be created by 160saving state and modifying it, to later restore it again afterwards. 161 162=cut 163 164=head1 CONSTRUCTOR 165 166=cut 167 168=head2 new 169 170 $rb = Tickit::RenderBuffer->new( %args ) 171 172Returns a new instance of a C<Tickit::RenderBuffer>. 173 174Takes the following named arguments: 175 176=over 8 177 178=item lines => INT 179 180=item cols => INT 181 182The size of the buffer area. 183 184=back 185 186=cut 187 188sub new 189{ 190 my $class = shift; 191 my %args = @_; 192 193 my $lines = $args{lines}; 194 my $cols = $args{cols}; 195 196 return $class->_xs_new( $lines, $cols ); 197} 198 199=head1 METHODS 200 201=cut 202 203=head2 lines 204 205=head2 cols 206 207 $lines = $rb->lines 208 209 $cols = $rb->cols 210 211Returns the size of the buffer area 212 213=cut 214 215=head2 line 216 217=head2 col 218 219 $line = $rb->line 220 221 $col = $rb->col 222 223Returns the current position of the virtual cursor, or C<undef> if it is not 224set. 225 226=cut 227 228=head2 save 229 230 $rb->save 231 232Pushes a new state-saving context to the stack, which can later be returned to 233by the C<restore> method. 234 235=cut 236 237=head2 savepen 238 239 $rb->savepen 240 241Pushes a new state-saving context to the stack that only stores the pen. This 242can later be returned to by the C<restore> method, but will only restore the 243pen. Other attributes such as the virtual cursor position will be unaffected. 244 245This may be more efficient for rendering runs of text in a different pen, than 246multiple calls to C<text> or C<erase> using the same pen. For a single call it 247is better just to pass a different pen directly. 248 249=cut 250 251=head2 restore 252 253 $rb->restore 254 255Pops and restores a saved state previously created with C<save>. 256 257=cut 258 259=head2 clip 260 261 $rb->clip( $rect ) 262 263Restricts the clipping rectangle of drawing operations to be no further than 264the limits of the given rectangle. This will apply to subsequent rendering 265operations but does not affect existing content, nor the actual rendering to 266the terminal. 267 268Clipping rectangles cumulative; each call further restricts the drawing 269region. To revert back to a larger drawing area, use the C<save> and 270C<restore> stack. 271 272=cut 273 274=head2 mask 275 276 $rb->mask( $rect ) 277 278Masks off the given area against any further changes. This will apply to 279subsequent rendering operations but does not affect the existing content, nor 280the actual rendering to the terminal. 281 282Areas within the clipping region may be arbitrarily masked. Masks are scoped 283to the depth of the stack they are applied at; once the C<restore> method is 284invoked, any masks applied since its corresponding C<save> will be removed. 285 286=head2 translate 287 288 $rb->translate( $downward, $rightward ) 289 290Applies a translation to the coordinate system used by C<goto> and the 291absolute-position methods C<*_at>. After this call, all positions used will be 292offset by the given amount. 293 294=cut 295 296=head2 reset 297 298 $rb->reset 299 300Removes any pending changes and reverts the C<RenderBuffer> to its default 301empty state. Undefines the virtual cursor position, resets the clipping 302rectangle, and clears the stack of saved state. 303 304=cut 305 306=head2 clear 307 308 $rb->clear( $pen ) 309 310Resets every cell in the buffer to an erased state. 311A shortcut to calling C<erase_at> for every line. 312 313=cut 314 315=head2 goto 316 317 $rb->goto( $line, $col ) 318 319Sets the position of the virtual cursor. 320 321=cut 322 323=head2 setpen 324 325 $rb->setpen( $pen ) 326 327Sets the rendering pen to use for drawing operations. If a pen is set then a 328C<$pen> argument is optional to any of the drawing methods. If a pen argument 329is supplied as well as having a stored pen, then the attributes are merged, 330with the directly-applied pen taking precedence. 331 332Successive calls to this method will replace the active pen used, but if there 333is a saved state on the stack it will be merged with the rendering pen of the 334most recent saved state. 335 336This method may be preferable to passing pens into multiple C<text> or 337C<erase> calls as it may be more efficient than merging the same pen on every 338call. If the original pen is still required afterwards, the C<savepen> / 339C<restore> pair may be useful. 340 341=cut 342 343=head2 skip_at 344 345 $rb->skip_at( $line, $col, $len ) 346 347Sets the range of cells given to a skipped state. No content will be drawn 348here, nor will any content existing on the terminal be erased. 349 350Initially, or after calling C<reset>, all cells are set to this state. 351 352=cut 353 354=head2 skip 355 356 $rb->skip( $len ) 357 358Sets the range of cells at the virtual cursor position to a skipped state, and 359updates the position. 360 361=cut 362 363=head2 skip_to 364 365 $rb->skip_to( $col ) 366 367Sets the range of cells from the virtual cursor position until before the 368given column to a skipped state, and updates the position to the column. 369 370If the position is already past this column then the cursor is moved backwards 371and no buffer changes are made. 372 373=cut 374 375=head2 skiprect 376 377 $rb->skiprect( $rect ) 378 379Sets the range of cells given by the rectangle to skipped state. 380 381=cut 382 383=head2 text_at 384 385 $cols = $rb->text_at( $line, $col, $text, $pen ) 386 387Sets the range of cells starting at the given position, to render the given 388text in the given pen. 389 390Returns the number of columns wide the actual C<$text> is (which may be more 391than was actually printed). 392 393=cut 394 395=head2 text 396 397 $cols = $rb->text( $text, $pen ) 398 399Sets the range of cells at the virtual cursor position to render the given 400text in the given pen, and updates the position. 401 402Returns the number of columns wide the actual C<$text> is (which may be more 403than was actually printed). 404 405=cut 406 407=head2 erase_at 408 409 $rb->erase_at( $line, $col, $len, $pen ) 410 411Sets the range of cells given to erase with the given pen. 412 413=cut 414 415=head2 erase 416 417 $rb->erase( $len, $pen ) 418 419Sets the range of cells at the virtual cursor position to erase with the given 420pen, and updates the position. 421 422=cut 423 424=head2 erase_to 425 426 $rb->erase_to( $col, $pen ) 427 428Sets the range of cells from the virtual cursor position until before the 429given column to erase with the given pen, and updates the position to the 430column. 431 432If the position is already past this column then the cursor is moved backwards 433and no buffer changes are made. 434 435=cut 436 437=head2 eraserect 438 439 $rb->eraserect( $rect, $pen ) 440 441Sets the range of cells given by the rectangle to erase with the given pen. 442 443=cut 444 445=head1 LINE DRAWING 446 447The C<RenderBuffer> supports storing line-drawing characters in cells, and can 448merge line segments where they meet, attempting to draw the correct character 449for the segments that meet in each cell. 450 451There are three exported constants giving supported styles of line drawing: 452 453=over 4 454 455=item * LINE_SINGLE 456 457A single, thin line 458 459=item * LINE_DOUBLE 460 461A pair of double, thin lines 462 463=item * LINE_THICK 464 465A single, thick line 466 467=back 468 469Note that linedrawing is performed by Unicode characters, and not every 470possible combination of line segments of differing styles meeting in a cell is 471supported by Unicode. The following sets of styles may be relied upon: 472 473=over 4 474 475=item * 476 477Any possible combination of only C<SINGLE> segments, C<THICK> segments, or 478both. 479 480=item * 481 482Any combination of only C<DOUBLE> segments, except cells that only have one of 483the four borders occupied. 484 485=item * 486 487Any combination of C<SINGLE> and C<DOUBLE> segments except where the style 488changes between C<SINGLE> to C<DOUBLE> on a vertical or horizontal run. 489 490=back 491 492Other combinations are not directly supported (i.e. any combination of 493C<DOUBLE> and C<THICK> in the same cell, or any attempt to change from 494C<SINGLE> to C<DOUBLE> in either the vertical or horizontal direction). To 495handle these cases, a cell may be rendered with a substitution character which 496replaces a C<DOUBLE> or C<THICK> segment with a C<SINGLE> one within that 497cell. The effect will be the overall shape of the line is retained, but close 498to the edge or corner it will have the wrong segment type. 499 500Conceptually, every cell involved in line drawing has a potential line segment 501type at each of its four borders to its neighbours. Horizontal lines are drawn 502though the vertical centre of each cell, and vertical lines are drawn through 503the horizontal centre. 504 505There is a choice of how to handle the ends of line segments, as to whether 506the segment should go to the centre of each cell, or should continue through 507the entire body of the cell and stop at the boundary. By default line segments 508will start and end at the centre of the cells, so that horizontal and vertical 509lines meeting in a cell will form a neat corner. When drawing isolated lines 510such as horizontal or vertical rules, it is preferable that the line go right 511through the cells at the start and end. To control this behaviour, the 512C<$caps> bitmask is used. C<CAP_START> and C<CAP_END> state that the line 513should consume the whole of the start or end cell, respectively; C<CAP_BOTH> 514is a convenient shortcut specifying both behaviours. 515 516A rectangle may be formed by combining two C<hline_at> and two C<vline_at> 517calls, without end caps: 518 519 $rb->hline_at( $top, $left, $right, $style, $pen ); 520 $rb->hline_at( $bottom, $left, $right, $style, $pen ); 521 $rb->vline_at( $top, $bottom, $left, $style, $pen ); 522 $rb->vline_at( $top, $bottom, $right, $style, $pen ); 523 524=cut 525 526=head2 hline_at 527 528 $rb->hline_at( $line, $startcol, $endcol, $style, $pen, $caps ) 529 530Draws a horizontal line between the given columns (both are inclusive), in the 531given line style, with the given pen. 532 533=cut 534 535=head2 vline_at 536 537 $rb->vline_at( $startline, $endline, $col, $style, $pen, $caps ) 538 539Draws a vertical line between the centres of the given lines (both are 540inclusive), in the given line style, with the given pen. 541 542=cut 543 544=head2 linebox_at 545 546 $rb->linebox_at( $startline, $endline, $startcol, $endcol, $style, $pen ) 547 548A convenient shortcut to calling two C<hline_at> and two C<vline_at> in order 549to draw a rectangular box. 550 551=cut 552 553sub linebox_at 554{ 555 my $self = shift; 556 my ( $startline, $endline, $startcol, $endcol, $style, $pen ) = @_; 557 558 $self->hline_at( $startline, $startcol, $endcol, $style, $pen ); 559 $self->hline_at( $endline, $startcol, $endcol, $style, $pen ); 560 561 $self->vline_at( $startline, $endline, $startcol, $style, $pen ); 562 $self->vline_at( $startline, $endline, $endcol, $style, $pen ); 563} 564 565=head2 char_at 566 567 $rb->char_at( $line, $col, $codepoint, $pen ) 568 569Sets the given cell to render the given Unicode character (as given by 570codepoint number, not character string) in the given pen. 571 572=cut 573 574=head2 char 575 576 $rb->char( $codepoint, $pen ) 577 578Sets the cell at the virtual cursor position to render the given Unicode 579character (as given by codepoint number, not character string) in the given 580pen, and updates the position. 581 582While this is also achieveable by the C<text> and C<text_at> methods, these 583methods are implemented without storing a text segment, so can be more 584efficient than many single-column wide C<text_at> calls. 585 586=cut 587 588=head2 copyrect 589 590=head2 moverect 591 592 $rb->copyrect( $dest, $src ) 593 594 $rb->moverect( $dest, $src ) 595 596Copies (or moves) buffered content from one rectangular region to another. 597The two regions may overlap. 598 599The move operation is identical to the copy operation followed by setting the 600vacated areas of the source rectangle not covered by the destination to 601skipping state. 602 603=cut 604 605=head2 get_cell 606 607 $cell = $rb->get_cell( $line, $col ) 608 609Returns a structure containing the content stored in the given cell. The 610C<$cell> structure responds to the following methods: 611 612=over 4 613 614=item $cell->char 615 616On a skipped cell, returns C<undef>. On a text or char cell, returns the 617unicode codepoint number. On a line or erased cell, returns 0. 618 619=item $cell->linemask 620 621On a line cell, returns a representation of the line segments in the cell. 622This is a sub-structure with four fields; C<north>, C<south>, C<east>, C<west> 623to represent the four cell borders; the value of each is either zero, or one 624of the C<LINE_> constants. 625 626On any other kind of cell, returns C<undef>. 627 628=item $cell->pen 629 630Returns the C<Tickit::Pen> for non-skipped cells, or C<undef> for skipped 631cells. 632 633=back 634 635=cut 636 637readonly_struct Cell => [qw( char linemask pen )]; 638readonly_struct LineMask => [qw( north south east west )]; 639 640sub get_cell 641{ 642 my $self = shift; 643 my ( $line, $col ) = @_; 644 645 my ( $text, $pen, $north, $south, $east, $west ) = $self->_xs_get_cell( $line, $col ); 646 647 if( !defined $text ) { 648 # SKIP 649 return Cell( undef, undef, undef ); 650 } 651 if( !length $text ) { 652 # ERASE 653 return Cell( 0, undef, $pen ); 654 } 655 if( !defined $north ) { 656 # TEXT or CHAR 657 return Cell( ord $text, undef, $pen ); 658 } 659 else { 660 # LINE 661 return Cell( 0, LineMask( $north, $south, $east, $west ), $pen ); 662 } 663} 664 665=head2 flush_to_term 666 667 $rb->flush_to_term( $term ) 668 669Renders the stored content to the given L<Tickit::Term>. After this, the 670buffer will be cleared and reset back to initial state. 671 672=cut 673 674=head1 AUTHOR 675 676Paul Evans <leonerd@leonerd.org.uk> 677 678=cut 679 6800x55AA; 681