File Coverage

blib/lib/Term/Graille/Interact.pm
Criterion Covered Total %
statement 21 116 18.1
branch 0 40 0.0
condition 0 21 0.0
subroutine 7 21 33.3
pod 8 13 61.5
total 36 211 17.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Term::Graille::Interact
4              
5             Allows user interaction in Graille Applications (or perhaps on any
6             pterminal application). Depends on Term::ReadKey and Time::HiRes;
7             When integrated with Term::Graille::Menu allows a modal drop down menu
8             that can be navigated using key presses.
9              
10             =head1 SYNOPSIS
11              
12             my $io=Term::Graille::Interact->new();
13             $io->addAction( # add action for key press
14             "Am", # Am is returned for up arrow
15             {note=>"up arrow:cursor up ", # For drawing a menu
16             proc=>sub{my $self,@args)=@_ ...} # the action
17             } );
18            
19             $io->run($io,@args); # start trapping keypresses
20             ...
21             $io->stop(); # stop
22            
23             =cut
24              
25             package Term::Graille::Interact;
26              
27             our $VERSION=0.10;
28              
29 1     1   1015 use strict; use warnings;
  1     1   2  
  1         28  
  1         5  
  1         2  
  1         30  
30 1     1   469 use lib "../lib";
  1         690  
  1         5  
31 1     1   129 use Time::HiRes ("sleep"); # allow fractional sleeps
  1         2  
  1         6  
32 1     1   91 use utf8; # allow utf characters in print
  1         2  
  1         9  
33             binmode STDOUT, ":utf8";
34 1     1   564 use Term::ReadKey; # allow reading from keyboard
  1         2106  
  1         75  
35 1     1   7 use Term::Graille qw/colour paint printAt cursorAt clearScreen border/;
  1         2  
  1         1584  
36              
37              
38              
39             my $namedKeys={
40             10 =>"enter",
41             9 =>"tab",
42             "[Z" =>"shifttab",
43             "[2~"=>"insert",
44             "[3~"=>"delete",
45             "[H"=>"home",
46             "[F"=>"end",
47             "[5~"=>"pgup",
48             "[6~"=>"pgdn",
49             127=>"backspace",
50             };
51              
52             =head1 FUNCTIONS
53              
54             =cut
55              
56             =head3 Cnew(%params)>
57              
58             Creates a new IO object for user interaction.
59             C<"Main">, is default interaction profile, each active widget
60             declkares its own interaction profile (responses to key presses)
61              
62             =cut
63              
64             sub new{
65 0     0 1   my $class = shift; #
66 0           my $self={};
67 0           $self->{refreshRate}=20;
68 0           $self->{key}="";
69 0           $self->{objects}={}; # hash containing objects that need userIO
70 0           $self->{activeObject}=""; # active object
71 0           ($self->{terminalWidth},$self->{terminalHeight},$self->{terminalXPixels},$self->{terminalYPixels})=GetTerminalSize;
72 0           $self->{cursor}=[0,0];
73 0           $self->{debug}=1;
74 0           $self->{debugCursor}=[20,45];
75 0           $self->{keyBuffer}="";
76 0           $self->{gV}={}; #contains global variables that are accessible from one object to next
77 0           bless $self,$class;
78 0           return $self;
79             }
80              
81             =head3 CaddObject($menu,%params)>
82              
83             Adds a user interaction object. params are:-
84              
85             objectId:Id of the object if not set this is automatically generated
86              
87             object: the reference to object REQUIRED
88              
89             actions: The key-press actions for this object when it is active
90              
91             trigger:
92              
93            
94             =cut
95              
96             sub addObject{
97 0     0 1   my ($self,%params)=@_;
98 0           my ($objectId,$object,$actions,$trigger)=@params{qw/objectId object actions trigger/};
99 0   0       $objectId//=$self->newId();
100 0           $self->{objects}->{$objectId}=$object;
101 0   0       $self->{objects}->{$objectId}->{objectId}//=$objectId;
102 0   0       $self->{objects}->{$objectId}->{actions}=$actions//{};
103 0 0         $self->{triggers}->{$trigger}=$objectId if $trigger;
104 0           return $objectId;
105             }
106              
107             =head3 C<$io-EaddAction($objectId,$key,$actionData)>
108              
109             Determines what happens when a key is pressed for a specific object. Functions in the
110             users scripts have to be "fully qualified" e.g. C<&main::function()>
111              
112             $io->addAction("menu","s",{note=>"s key saves sprite",proc=>sub{
113             my ($self,$canvas,$sprite,$cursor)=@_; # these are the objects passed as parameters
114             &main::saveSprite($sprite);
115             &main::flashCursor($sprite,$cursor);
116             &main::restoreIO();},} );
117            
118              
119             =cut
120              
121             sub addAction{
122 0     0 1   my ($self,$objectId,$key,$actionData)=@_;
123 0           my %args=%$actionData;
124 0 0 0       if ($objectId && $self->{objects}->{$objectId}){
125 0           foreach my $k (keys %args){
126 0           $self->{objects}->{$objectId}->{actions}->{$key}->{$k}=$args{$k};
127             }
128             }
129             else{
130 0           foreach my $k (keys %args){
131 0           $self->{actions}->{$key}->{$k}=$args{$k};
132             }
133             }
134            
135             }
136              
137              
138             =head3 CupdateAction($menu,$action)>
139              
140             Adds a routine that is executed every interaction cycle
141             e.g for animations
142              
143             =cut
144              
145             sub updateAction{
146 0     0 1   my ($self,$action)=@_;
147 0           $self->{actions}->{update}=$action;
148             }
149              
150             sub stopUpdates{
151 0     0 0   my ($self,)=@_;
152 0           delete $self->{actions}->{update};
153            
154             }
155              
156             sub newId{
157 0     0 0   my $self=shift;
158 0           my $index=0;
159 0           $index++ while (exists $self->{objects}->{"o$index"});
160 0           return "o$index";
161             }
162              
163             =head3 C<$io-EaddActionSet(,$actionSet)>
164              
165             allows multiple $key actions to be set/changed as a set.
166             For example tyhe arrow keys may have one purpose navigating a menu and
167             another set of actions in the game. Toggling between the game and menu
168             would need the keys to be mapped to different actions, and this alows
169             the actions to be swapped byu bundling the actions into sets.
170              
171             my $actionSet=[["s",{note=>"s key saves sprite",proc=>sub{
172             my ($self,$canvas,$sprite,$cursor)=@_;
173             &main::saveSprite($sprite);
174             &main::flashCursor($sprite,$cursor);
175             &main::restoreIO();},} ],
176             # [$key2, $actionData2], etc
177             ]
178              
179             =cut
180              
181              
182             sub addActionSet{
183 0     0 1   my ($self,$objectId,$actionSet)=@_;
184 0           foreach my $actionPair (@$actionSet){ # pair of keymap and action;
185 0           $self->addAction($objectId,@$actionPair)
186             }
187             }
188              
189             =head3 C<$io-Erun()>
190              
191             Initiating the capture of the key presses that may trigger actions.
192              
193             =cut
194              
195             sub run{
196 0     0 1   my ($self,$objectId)=@_;
197 0   0       $self->{activeObject}=$objectId//"";
198 0           ReadMode 'cbreak';
199            
200 0           while($self->{activeObject} ne "stop"){ # setting $io->{activeObject} to "stop" exits loop
201 0           sleep 1/$self->{refreshRate};
202 0           $self->{key} = ReadKey(-1); # -1 means non-blocking read
203 0           my $pressed="";
204 0 0         if (defined $self->{key}){
205 0           my $esc="";
206 0           my $OrdKey = ord($self->{key});
207 0 0 0       if ($OrdKey ==27){$esc=get_escape_sequence()//"esc"};
  0            
208 0 0         if (exists $namedKeys->{$OrdKey}){$pressed=$namedKeys->{$OrdKey}}
  0 0          
209 0           elsif (exists $namedKeys->{$esc}){$pressed=$namedKeys->{$esc}}
210 0 0         else{$pressed= ($esc ne "")?$esc:chr($OrdKey);};
211            
212 0 0         if ($self->{activeObject} ne ""){ # mode is widget;
213 0           $self->debugMessage("key pressed=$OrdKey $pressed ".$self->{activeObject}." ");
214 0 0         if (defined $self->{objects}->{$self->{activeObject}}->{keyAction}->{$pressed}){ # pre defined key actions
    0          
215 0           $self->{objects}->{$self->{activeObject}}->{keyAction}->{$pressed}->($self->{activeObject},$self->{gV});
216             }
217             elsif($self->{objects}->{$self->{activeObject}}->{keyAction}->{others}){ # if an action for undefined keys exists
218 0           $self->{objects}->{$self->{activeObject}}->{keyAction}->{others}->($self->{objects}->{$self->{activeObject}},$pressed,$self->{gV});
219             }
220             else { # otherwise collect the keys pressed in a buffer
221 0           $self->{objects}->{activeObject}->{keyBuffer}.=$pressed
222             }
223             }
224             else { # if mode is main
225 0           my $mode="(MAIN)";
226 0 0         if (defined $self->{actions}->{$pressed}->{proc}){
    0          
    0          
227             $self->{actions}->{$pressed}->{proc}->($self->{gV})
228 0           }
229             elsif(exists $self->{triggers}->{$pressed}){
230 0           $mode="($self->{triggers}->{$pressed})";
231 0           $self->start($self->{triggers}->{$pressed});
232             }
233             elsif(exists $self->{actions}->{others}){
234 0           $self->{actions}->{others}->{proc}->($pressed,$self->{gV});
235             }
236             else { # otherwise collect the keys pressed in a buffer
237 0           $self->{keyBuffer}.=$pressed;
238             }
239            
240 0           $self->debugMessage("key pressed=$OrdKey $pressed $mode ");
241             }
242             }
243            
244 0 0         $self->{actions}->{update}->() if $self->{actions}->{update};
245             }
246 0           ReadMode 'normal';
247             }
248              
249             sub debugMessage{
250 0     0 0   my ($self,$msg)=@_;
251 0           printAt (@{$self->{debugCursor}},$msg) if ($self->{debug})
252 0 0         }
253              
254             sub get_escape_sequence {
255 0     0 0   my $esc;
256 0           while ( my $key = ReadKey(-1) ) {
257 0           $esc .= $key;
258 0 0         last if ( $key =~ /[a-z~]/i );
259             }
260 0           return $esc;
261             }
262              
263              
264             =head3 C<$io-Estart($objectId,$params)>
265              
266             Starts an object that consumes keypresses. $params is a hash ref that is
267             passed to the object to allow customusation
268              
269            
270             =cut
271              
272             sub start{
273 0     0 1   my ($self,$objectId,$params)=@_;
274 0 0         close($self->{activeObject}) if $self->{activeObject};
275 0           $self->{activeObject}=$objectId;
276 0 0         $self->{objects}->{$objectId}->{params}=$params if defined $params;
277 0     0     my $closer=sub{$self->close()};
  0            
278 0           $self->{objects}->{$objectId}->{close}=$closer; # closer function to object
279 0           $self->{objects}->{$objectId}->draw();
280             }
281              
282              
283             =head3 C<$io-Eclose()>
284              
285             closes currently active actually by calling Term::Graile::Interacts close(),
286             this has been set during s Term::Graile::Interacts start($objectId)
287            
288             =cut
289              
290             sub close{
291 0     0 1   my ($self)=@_;
292             $self->{objects}->{$self->{activeObject}}->close() # if the object has own close function
293 0 0 0       if ( $self->{objects}->{$self->{activeObject}}&& (ref $self->{objects}->{$self->{activeObject}} ne "HASH") && $self->{objects}->{$self->{activeObject}}->can("close"));
      0        
294 0 0         delete $self->{objects}->{$self->{activeObject}} if ($self->{objects}->{$self->{activeObject}}->{transient});
295 0           $self->{activeObject}="";
296             }
297              
298             sub stop{
299 0     0 0   my $self=shift;
300 0           $self->{mode}="stop";
301 0           ReadMode 'normal';
302             }