File Coverage

blib/lib/Tickit/Widget/SegmentDisplay.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
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 -- leonerd@leonerd.org.uk
5              
6             package Tickit::Widget::SegmentDisplay;
7              
8 1     1   771 use strict;
  1         3  
  1         34  
9 1     1   6 use warnings;
  1         1  
  1         29  
10 1     1   29 use 5.010; # //
  1         4  
  1         34  
11 1     1   5 use base qw( Tickit::Widget );
  1         2  
  1         528  
12 1     1   363 use Tickit::Style;
  0            
  0            
13              
14             our $VERSION = '0.02';
15              
16             use Carp;
17              
18             # The 7 segments are
19             # AAA
20             # F B
21             # F B
22             # GGG
23             # E C
24             # E C
25             # DDD
26             #
27             # B,C,E,F == 2cols wide
28             # A,D,G == 1line tall
29              
30             =head1 NAME
31              
32             C - show a single character like a segmented display
33              
34             =head1 DESCRIPTION
35              
36             This class provides a widget that immitates a segmented LED or LCD display. It
37             shows a single character by lighting or shading fixed rectangular bars.
38              
39             =head1 STYLE
40              
41             The default style pen is used as the widget pen, though only the background
42             colour will actually matter as the widget does not directly display text.
43              
44             The following style keys are used:
45              
46             =over 4
47              
48             =item lit => COLOUR
49              
50             =item unlit => COLOUR
51              
52             Colour descriptions (index or name) for the lit and unlight segments of the
53             display.
54              
55             =back
56              
57             =cut
58              
59             style_definition base =>
60             lit => "red",
61             unlit => 16+36;
62              
63             use constant WIDGET_PEN_FROM_STYLE => 1;
64              
65             =head1 CONSTRUCTOR
66              
67             =cut
68              
69             =head2 $segmentdisplay = Tickit::Widget::SegmentDisplay->new( %args )
70              
71             Constructs a new C object.
72              
73             Takes the following named arguments
74              
75             =over 8
76              
77             =item value => STR
78              
79             Sets an initial value.
80              
81             =item type => STR
82              
83             The type of display. Supported types are:
84              
85             =over 4
86              
87             =item seven
88              
89             A 7-segment bar display
90              
91             =item colon
92              
93             A static C<:>
94              
95             =back
96              
97             =back
98              
99             =cut
100              
101             my %types = (
102             seven => [qw( 7 )],
103             colon => [qw( : )],
104             );
105              
106             sub new
107             {
108             my $class = shift;
109             my %args = @_;
110             my $self = $class->SUPER::new( %args );
111              
112             my $type = $args{type} // "seven";
113             my $method;
114             foreach my $typename ( keys %types ) {
115             $type eq $typename and $method = $typename, last;
116             $type eq $_ and $method = $typename, last for @{ $types{$typename} };
117             }
118             defined $method or croak "Unrecognised type name '$type'";
119              
120             $self->{render_method} = $self->can( "render_${method}_to_rb" );
121              
122             $self->{value} = $args{value} // "";
123              
124             $self->on_style_changed_values(
125             lit => [ undef, $self->get_style_values( "lit" ) ],
126             unlit => [ undef, $self->get_style_values( "unlit" ) ],
127             );
128              
129             return $self;
130             }
131              
132             # ADG + atleast 1 line each for FB and EC
133             sub lines { 3 + 2 }
134              
135             # FE, BC + atleast 2 columns for AGD
136             sub cols { 4 + 2 }
137              
138             =head1 ACCESSORS
139              
140             =cut
141              
142             =head2 $value = $segmentdisplay->value
143              
144             =head2 $segmentdisplay->set_value( $value )
145              
146             Return or set the character on display
147              
148             =cut
149              
150             sub value
151             {
152             my $self = shift;
153             return $self->{value};
154             }
155              
156             sub set_value
157             {
158             my $self = shift;
159             ( $self->{value} ) = @_;
160             $self->redraw;
161             }
162              
163             sub on_style_changed_values
164             {
165             my $self = shift;
166             my %values = @_;
167              
168             $self->{lit_pen} = Tickit::Pen::Immutable->new( bg => $values{lit}[1] ) if $values{lit};
169             $self->{unlit_pen} = Tickit::Pen::Immutable->new( bg => $values{unlit}[1] ) if $values{unlit};
170             }
171              
172             my %segments = (
173             0 => "ABCDEF ",
174             1 => " BC ",
175             2 => "AB DE G",
176             3 => "ABCD G",
177             4 => " BC FG",
178             5 => "A CD FG",
179             6 => "A CDEFG",
180             7 => "ABC ",
181             8 => "ABCDEFG",
182             9 => "ABCD FG",
183             );
184              
185             sub _pen_for_seg
186             {
187             my $self = shift;
188             my ( $segment ) = @_;
189              
190             my $segments = $segments{$self->value} or return $self->{unlit_pen};
191              
192             my $lit = substr( $segments, ord($segment) - ord("A"), 1 ) ne " ";
193             return $lit ? $self->{lit_pen} : $self->{unlit_pen};
194             }
195              
196             sub reshape
197             {
198             my $self = shift;
199             my $win = $self->window or return;
200              
201             my $lines = $win->lines;
202             my $cols = $win->cols;
203             my ( $top, $left ) = ( 0, 0 );
204              
205             $self->{AGD_col} = $left + 2;
206             $self->{AGD_width} = $cols - 4;
207              
208             $self->{FE_col} = $left;
209             $self->{BC_col} = $left + $cols - 2;
210              
211             $self->{A_line} = $top;
212             $self->{G_line} = $top + int( ( $lines - 1 + 0.5 ) / 2 );
213             $self->{D_line} = $top + $lines - 1;
214             }
215              
216             sub render_to_rb
217             {
218             my $self = shift;
219             my ( $rb, $rect ) = @_;
220              
221             $rb->eraserect( $rect );
222              
223             $self->{render_method}->( $self, $rb, $rect );
224             }
225              
226             # 7-Segment
227             sub render_seven_to_rb
228             {
229             my $self = shift;
230             my ( $rb ) = @_;
231              
232             $rb->erase_at( $self->{A_line}, $self->{AGD_col}, $self->{AGD_width}, $self->_pen_for_seg( "A" ) );
233             $rb->erase_at( $self->{G_line}, $self->{AGD_col}, $self->{AGD_width}, $self->_pen_for_seg( "G" ) );
234             $rb->erase_at( $self->{D_line}, $self->{AGD_col}, $self->{AGD_width}, $self->_pen_for_seg( "D" ) );
235              
236             my ( $F_pen, $B_pen ) = ( $self->_pen_for_seg( "F" ), $self->_pen_for_seg( "B" ) );
237             foreach my $line ( $self->{A_line}+1 .. $self->{G_line}-1 ) {
238             $rb->erase_at( $line, $self->{FE_col}, 2, $F_pen );
239             $rb->erase_at( $line, $self->{BC_col}, 2, $B_pen );
240             }
241              
242             my ( $E_pen, $C_pen ) = ( $self->_pen_for_seg( "E" ), $self->_pen_for_seg( "C" ) );
243             foreach my $line ( $self->{G_line}+1 .. $self->{D_line}-1 ) {
244             $rb->erase_at( $line, $self->{FE_col}, 2, $E_pen );
245             $rb->erase_at( $line, $self->{BC_col}, 2, $C_pen );
246             }
247             }
248              
249             # Static double-dot colon
250             sub render_colon_to_rb
251             {
252             my $self = shift;
253             my ( $rb ) = @_;
254              
255             my $col = 2 + int( $self->{AGD_width} / 2 );
256             $rb->erase_at( int( ($self->{A_line} + $self->{G_line}) / 2 ), $col, 2, $self->{lit_pen} );
257             $rb->erase_at( int( ($self->{G_line} + $self->{D_line}) / 2 ), $col, 2, $self->{lit_pen} );
258             }
259              
260             =head1 AUTHOR
261              
262             Paul Evans
263              
264             =cut
265              
266             0x55AA;