/
Print.pm6
356 lines (277 loc) · 10 KB
/
Print.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
use v6;
unit class Terminal::Print;
=begin pod
=title Terminal::Print
=head1 Synopsis
L<Terminal::Print> implements an abstraction layer for printing characters to
terminal screens with full Unicode support and -- crucially -- the ability to
print from concurrent threads. The idea is to provide all the necessary
mechanical details while leaving the actual so called 'TUI' abstractions to
higher level libraries.
Obvious applications include snake clones, rogue engines and golfed art works :)
Oh, and Serious Monitoring Apps, of course.
=head1 Usage
L<Terminal::Print> creates you an object for you when you import it, stored in
C<$Terminal::Print::T>. It also creates a constant C<T> for you in the C<OUR::>
scope.
Thus common usage would look like this:
=for code
T.initialize-screen;
T.print-string(20, 20, DateTime.now);
T.shutdown-screen;
=head1 Miscellany
=head2 Where are we at now?
All the features you can observe while running C<perl6 t/basics.t> work using
the new react/supply based L<Terminal::Print::Grid>. If you run that test file,
you will notice that C<Terminal::Print> is needing a better test harness.
Part of that is getting a C<STDERR> or some such pipe going, and printing state/
That will make debugging a lot easier.
Testing a thing that is primarily designed to print to a screen seems a bit
difficult anyway. I almost think we should make it interactive. 'Did you see a
screen of hearts?'
So: async (as mentioned above), testing, and debugging are current pain points.
Contributions welcome.
=head2 Why not just use L<NativeCall> and C<ncurses>?
I tried that first and it wasn't any fun. C<ncurses> unicode support is
admirable considering the age and complexity of the library, but it
still feels bolted on.
C<ncurses> is not re-entrant, either, which would nix one of the main benefits
we might be able to get from using Perl 6 -- easy async abstractions.
=end pod
use Terminal::Print::Grid;
use Terminal::Print::Widget;
has Terminal::Print::Grid $.current-grid handles 'indices';
has Terminal::Print::Grid @.grids;
has %!grid-name-map;
has %!root-widget-map{Terminal::Print::Grid};
has $.columns;
has $.rows;
use Terminal::Print::Commands;
subset Valid::X of Int is export where * < %T::attributes<columns>;
subset Valid::Y of Int is export where * < %T::attributes<rows>;
subset Valid::Char of Str is export where *.chars == 1;
has Terminal::Print::CursorProfile $.cursor-profile;
has $.move-cursor;
method new( :$cursor-profile = 'ansi' ) {
my $columns = +%Terminal::Print::Commands::attributes<columns>;
my $rows = +%Terminal::Print::Commands::attributes<rows>;
my $move-cursor = move-cursor-template($cursor-profile);
my $current-grid = Terminal::Print::Grid.new( $columns, $rows, :$move-cursor );
self.bless( :$columns, :$rows, :$current-grid,
:$cursor-profile, :$move-cursor );
}
submethod BUILD( :$!current-grid, :$!columns, :$!rows, :$!cursor-profile, :$!move-cursor ) {
push @!grids, $!current-grid;
# set up a tap on SIGINT so that we can cleanly shutdown, restoring the previous screen and cursor
signal(SIGINT).tap: {
@!grids>>.disable;
self.shutdown-screen;
die "Encountered a SIGINT. Cleaning up the screen and exiting...";
}
}
method root-widget() {
my $grid = self.current-grid;
%!root-widget-map{$grid} ||= Terminal::Print::Widget.new-from-grid($grid);
}
method add-grid( $name?, :$new-grid = Terminal::Print::Grid.new( $!columns, $!rows, :$!move-cursor ) ) {
push @!grids, $new-grid;
if $name {
%!grid-name-map{$name} = +@!grids-1;
}
$new-grid;
}
multi method switch-grid( Int $index, :$blit ) {
die "Grid index $index does not exist" unless @!grids[$index]:exists;
self.blit($index) if $blit;
$!current-grid = @!grids[$index];
}
multi method switch-grid( Str $name, :$blit ) {
die "No grid has been named $name" unless my $index = %!grid-name-map{$name};
self.blit($index) if $blit;
$!current-grid = @!grids[$index];
}
method blit( $grid-identifier = 0 ) {
self.clear-screen;
self.print-grid($grid-identifier);
}
# 'clear' will also work through the FALLBACK
method clear-screen {
print-command <clear>;
}
method initialize-screen {
print-command <save-screen>;
print-command <hide-cursor>;
print-command <clear>;
}
method shutdown-screen {
print-command <clear>;
print-command <restore-screen>;
print-command <show-cursor>;
}
method print-command( $command ) {
print-command($command, $!cursor-profile);
}
# AT-POS hands back a Terminal::Print::Column
# $b[$x]
# Because we have AT-POS on the column object as well,
# we get
# $b[$x][$y]
method AT-POS( $column-idx ) {
$!current-grid.grid[ $column-idx ];
}
# AT-KEY returns the Terminal::Print::Grid.grid of whichever the key specifies
# $b<specific-grid>[$x][$y]
method AT-KEY( $grid-identifier ) {
self.grid( $grid-identifier );
}
multi method CALL-ME($x, $y) {
$!current-grid.print-cell($x, $y);
}
multi method CALL-ME($x, $y, %c) {
$!current-grid.print-cell($x, $y, %c);
}
multi method CALL-ME($x, $y, $c) {
$!current-grid.print-string($x, $y, $c);
}
multi method FALLBACK( Str $command-name where { %T::human-command-names{$_} } ) {
print-command( $command-name );
}
# multi method sugar:
# @!grids and @!buffers can both be accessed by index or name (if it has
# one). The name is optionally supplied when calling .add-grid.
#
# In the case of @!grids, we pass back the grid array directly from the
# Terminal::Print::Grid object, actually notching both DWIM and DRY in one swoosh.
# because you can do things like $b.grid("background")[42][42] this way.
multi method grid() {
$!current-grid.grid;
}
multi method grid( Int $index ) {
@!grids[$index].grid;
}
multi method grid( Str $name ) {
die "No grid has been named $name" unless my $grid-index = %!grid-name-map{$name};
@!grids[$grid-index].grid;
}
#### grid-object stuff
# Sometimes you simply want the object back (for stringification, or
# introspection on things like column-range)
multi method grid-object( Int $index ) {
@!grids[$index];
}
multi method grid-object( Str $name ) {
die "No grid has been named $name" unless my $grid-index = %!grid-name-map{$name};
@!grids[$grid-index];
}
multi method print-cell( $x, $y ) {
$!current-grid.print-cell($x, $y);
}
multi method print-cell( $x, $y, Str $c ) {
$!current-grid.print-cell($x, $y, $c);
}
multi method print-cell( $x, $y, %c ) {
$!current-grid.print-cell($x, $y, %c);
}
multi method print-string( $x, $y, Str() $string) {
$!current-grid.print-string($x, $y, $string);
}
multi method print-string( $x, $y, Str() $string, $color) {
$!current-grid.print-string($x, $y, $string, $color);
}
method change-cell( $x, $y, Str $c ) {
$!current-grid.change-cell($x, $y, $c);
}
#### print-grid stuff
multi method print-grid( Int $index ) {
die "Grid index $index does not exist" unless @!grids[$index]:exists;
print @!grids[$index];
}
multi method print-grid( Str $name ) {
die "No grid has been named $name" unless my $index = %!grid-name-map{$name};
print @!grids[$index];
}
# method !clone-grid-index( $origin, $dest? ) {
# my $new-grid;
# if $dest {
# $new-grid := self.add-grid($dest, new-grid => @!grids[$origin].clone);
# } else {
# @!grids.push: @!grids[$origin].clone;
# }
# return $new-grid;
# }
#
# #### clone-grid stuff
#
# multi method clone-grid( Int $origin, Str $dest? ) {
# die "Invalid grid '$origin'" unless @!grids[$origin]:exists;
# self!clone-grid-index($origin, $dest);
# }
#
# multi method clone-grid( Str $origin, Str $dest? ) {
# die "Invalid grid '$origin'" unless my $grid-index = %!grid-name-map{$origin};
# self!clone-grid-index($grid-index, $dest);
# }
method Str {
~$!current-grid;
}
method gist {
"\{ cols: {self.columns} rows: {self.rows} which: {self.WHICH} grid: {self.current-grid.WHICH} \}";
}
=begin Golfing
The golfing mechanism is minimal. Further golfing functionality may be added via third party modules,
but the following features seemed to fulfill a 'necessary minimum' set of golfing requirements:
- Not being subjected to a constructor command, certainly not against the full name of the class
+ Solved via 'T'
- Having a succinct subroutine form which can initialize and shutdown the screen automatically
+ Solved via 'draw'
- Easy access to .print-string, sleep, colorization, and the grid indices list. (Even easier than using T());
+ Solved via 'd', 'w', 'h', 'p', 'cl', 'ch', 'slp', 'fgc', 'bgc', 'in'
=end Golfing
our $T = PROCESS::<$TERMINAL> = Terminal::Print.new;
sub draw(Callable $block) is export {
my $drawn-promise = Promise.new;
start {
my $end-promise = Promise.new;
$T.initialize-screen;
$block($end-promise);
await $end-promise;
$T.shutdown-screen;
$drawn-promise.keep;
}
await $drawn-promise;
}
sub d($block) is export {
draw($block);
}
multi sub p($x, $y) is export {
$T.current-grid.print-string($x, $y);
}
multi sub p($x, $y, $string) is export {
$T.current-grid.print-string($x, $y, $string);
}
multi sub p($x, $y, $string, $color) is export {
$T.current-grid.print-string($x, $y, $string, $color);
}
multi sub ch($x, $y, $char) is export {
$T.current-grid.change-cell($x, $y, $char);
}
multi sub ch($x, $y, $char, $color) is export {
$T.current-grid.change-cell($x, $y, %(:$char, :$color) );
}
multi sub cl($x, $y, $char) is export {
$T.current-grid.print-cell($x, $y, $char);
}
multi sub cl($x, $y, $char, $color) is export {
$T.current-grid.print-cell($x, $y, %(:$char, :$color) );
}
sub slp($seconds) is export {
sleep $seconds;
}
my package EXPORT::DEFAULT {
OUR::{ 'T' } := $Terminal::Print::T;
OUR::{ 'w' } := $Terminal::Print::T.columns;
OUR::{ 'h' } := $Terminal::Print::T.rows;
OUR::{ 'in' } := $Terminal::Print::T.indices;
OUR::{ 'fgc' } := @Terminal::Print::Commands::fg_colors;
OUR::{ 'bgc' } := @Terminal::Print::Commands::bg_colors;
}