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