File Coverage

blib/lib/Tickit/Widget/FloatBox.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.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, 2014 -- leonerd@leonerd.org.uk
5              
6             package Tickit::Widget::FloatBox;
7              
8 1     1   981 use strict;
  1         3  
  1         47  
9 1     1   7 use warnings;
  1         2  
  1         38  
10 1     1   16 use base qw( Tickit::ContainerWidget );
  1         2  
  1         803  
11              
12             our $VERSION = '0.02';
13              
14             use Carp;
15              
16             =head1 NAME
17              
18             C - manage a collection of floating widgets
19              
20             =head1 SYNOPSIS
21              
22             TODO
23              
24             =head1 DESCRIPTION
25              
26             This container widget maintains a collection of floating widgets that can be
27             displayed over the top of a single base widget. The box itself is entirely
28             occupied by the base widget, and by default when no floats are created or
29             displayed it will behave essentially invisibly, as though the box were not
30             there and the base widget was an immediate child of the container the floatbox
31             is inside.
32              
33             =cut
34              
35             =head1 CONSTRUCTOR
36              
37             =cut
38              
39             =head2 $floatbox = Tickit::Widget::FloatBox->new( %args )
40              
41             Constructs a new C object.
42              
43             Takes the following named arguments in addition to those taken by the base
44             L constructor.
45              
46             =over 8
47              
48             =item base_child => Tickit::Widget
49              
50             The main L instance to use as the base.
51              
52             =back
53              
54             =cut
55              
56             sub new
57             {
58             my $class = shift;
59             my %args = @_;
60              
61             my $self = $class->SUPER::new( %args );
62              
63             $self->set_base_child( $args{base_child} ) if $args{base_child};
64             $self->{floats} = [];
65              
66             return $self;
67             }
68              
69             =head1 ACCESSORS
70              
71             =cut
72              
73             sub children
74             {
75             my $self = shift;
76             my @children;
77              
78             push @children, $self->base_child if $self->base_child;
79             push @children, $_->child for @{ $self->{floats} };
80              
81             return @children;
82             }
83              
84             sub lines
85             {
86             my $self = shift;
87             return $self->base_child ? $self->base_child->requested_lines : 1;
88             }
89              
90             sub cols
91             {
92             my $self = shift;
93             return $self->base_child ? $self->base_child->requested_cols : 1;
94             }
95              
96             =head2 $base_child = $floatbox->base_child
97              
98             =head2 $floatbox->set_base_child( $base_child )
99              
100             Returns or sets the base widget to use.
101              
102             =cut
103              
104             sub base_child
105             {
106             my $self = shift;
107             return $self->{base_child};
108             }
109              
110             sub set_base_child
111             {
112             my $self = shift;
113             my ( $new ) = @_;
114              
115             if( my $old = $self->{base_child} ) {
116             $self->remove( $old );
117             }
118              
119             $self->{base_child} = $new;
120             $self->add( $new );
121              
122             if( my $win = $self->window ) {
123             $new->set_window( $win->make_sub( 0, 0, $win->lines, $win->cols ) );
124             }
125             }
126              
127             sub reshape
128             {
129             my $self = shift;
130              
131             return unless my $win = $self->window;
132              
133             if( my $child = $self->base_child ) {
134             if( $child->window ) {
135             $child->window->resize( $win->lines, $win->cols );
136             }
137             else {
138             $child->set_window( $win->make_sub( 0, 0, $win->lines, $win->cols ) );
139             }
140             }
141              
142             $self->_reshape_float( $_, $win ) for @{ $self->{floats} };
143              
144             $self->redraw;
145             }
146              
147             sub _reshape_float
148             {
149             my $self = shift;
150             my ( $float, $win ) = @_;
151              
152             my $child = $float->child;
153             my @geom = $float->_get_geom( $win->lines, $win->cols );
154              
155             if( my $childwin = $child->window ) {
156             $childwin->expose;
157             $childwin->change_geometry( @geom );
158             $childwin->expose;
159             }
160             else {
161             # TODO: Ordering?
162             # TODO: I want a ->make_hidden_float
163             $child->set_window( $win->make_float( @geom ) );
164             $child->window->hide if $float->{hidden};
165             }
166             }
167              
168             sub render_to_rb
169             {
170             my $self = shift;
171             my ( $rb, $rect ) = @_;
172              
173             return if $self->base_child;
174              
175             $rb->eraserect( $rect );
176             }
177              
178             =head2 $float = $floatbox->add_float( %args )
179              
180             Adds a widget as a floating child and returns a new C object. Takes the
181             following arguments:
182              
183             =over 8
184              
185             =item child => Tickit::Widget
186              
187             The child widget
188              
189             =item top, bottom, left, right => INT
190              
191             The initial geometry of the floating area. These follow the same behaviour as
192             the C method on the Float object.
193              
194             =item hidden => BOOL
195              
196             Optional. If true, the float starts off hidden initally, and must be shown by
197             the C method before it becomes visible.
198              
199             =back
200              
201             =cut
202              
203             sub add_float
204             {
205             my $self = shift;
206             my %args = @_;
207              
208             my $float = Tickit::Widget::FloatBox::Float->new(
209             $self, delete $args{child}, %args
210             );
211             push @{ $self->{floats} }, $float;
212              
213             $self->add( $float->child );
214              
215             if( my $win = $self->window ) {
216             $self->_reshape_float( $float, $win );
217             }
218              
219             return $float;
220             }
221              
222             sub _remove_float
223             {
224             my $self = shift;
225             my ( $float ) = @_;
226              
227             my $idx;
228             $self->{floats}[$_] == $float and $idx = $_, last for 0 .. $#{ $self->{floats} };
229             defined $idx or croak "Cannot remove float - not a member of the FloatBox";
230              
231             splice @{ $self->{floats} }, $idx, 1, ();
232              
233             $self->remove( $float->child );
234             }
235              
236             =head1 FLOATS
237              
238             The following objects represent a floating region as returned by the
239             C method.
240              
241             =cut
242              
243             package # hide
244             Tickit::Widget::FloatBox::Float;
245              
246             use Carp;
247              
248             sub new
249             {
250             my $class = shift;
251             my ( $fb, $child, %args ) = @_;
252              
253             my $self = bless {
254             fb => $fb,
255             child => $child,
256             hidden => delete $args{hidden} || 0,
257             }, $class;
258              
259             $self->move( %args );
260              
261             return $self;
262             }
263              
264             =head2 $child = $float->child
265              
266             Returns the child widget in the region.
267              
268             =cut
269              
270             sub child { shift->{child} }
271              
272             =head2 $float->move( %args )
273              
274             Redefines the area geometry of the region. Takes arguments named C,
275             C, C and C, each of which should either be a numeric
276             value, or C.
277              
278             The region must have at least one of C or C and at least one of
279             C or C defined, which will then fix the position of one corner of
280             the region. If the size is not otherwise determined by the geometry, it will
281             use the preferred size of the child widget. Any geometry argument may be
282             negative to count backwards from the limits of the parent.
283              
284             For example,
285              
286             # top-left corner
287             $float->move( top => 0, left => 0 )
288              
289             # top-right corner
290             $float->move( top => 0, right => -1 )
291              
292             # bottom 3 lines, flush left
293             $float->move( left => 0, top => -3, bottom => -1 )
294              
295             Any arguments not passed will be left unchanged; to specifically clear the
296             current value pass a value of C.
297              
298             =cut
299              
300             sub move
301             {
302             my $self = shift;
303             my %args = @_;
304              
305             exists $args{$_} and $self->{$_} = $args{$_} for qw( top bottom left right );
306              
307             defined $self->{top} or defined $self->{bottom} or
308             croak "A Float needs at least one of 'top' or 'bottom'";
309             defined $self->{left} or defined $self->{right} or
310             croak "A Float needs at least one of 'left' or 'right'";
311              
312             if( my $win = $self->{fb}->window ) {
313             $self->{fb}->_reshape_float( $self, $win );
314             }
315             }
316              
317             sub _get_geom
318             {
319             my $self = shift;
320             my ( $lines, $cols ) = @_;
321              
322             my $clines = $self->child->requested_lines;
323             my $ccols = $self->child->requested_cols;
324              
325             my ( $top, $bottom ) = _alloc_dimension( $self->{top}, $self->{bottom}, $lines, $clines );
326             my ( $left, $right ) = _alloc_dimension( $self->{left}, $self->{right}, $cols, $ccols );
327              
328             return ( $top, $left, $bottom-$top, $right-$left );
329             }
330              
331             sub _alloc_dimension
332             {
333             my ( $start, $end, $parentsz, $childsz ) = @_;
334              
335             # Need to off-by-one to allow -1 == right, etc..
336             defined and $_ < 0 and $_ += $parentsz+1 for $start, $end;
337              
338             $end = $start + $childsz if !defined $end;
339             $start = $end - $childsz if !defined $start;
340              
341             return ( $start, $end );
342             }
343              
344             =head2 $float->remove
345              
346             Removes the float from the FloatBox.
347              
348             =cut
349              
350             sub remove
351             {
352             my $self = shift;
353             $self->{fb}->_remove_float( $self );
354             }
355              
356             =head2 $float->hide
357              
358             Hide the float by hiding the window of its child widget.
359              
360             =cut
361              
362             sub hide
363             {
364             my $self = shift;
365             $self->{hidden} = 1;
366              
367             $self->child->window->hide if $self->child->window;
368             }
369              
370             =head2 $float->show
371              
372             Show the float by showing the window of its child widget. Undoes the effect
373             of C.
374              
375             =cut
376              
377             sub show
378             {
379             my $self = shift;
380             $self->{hidden} = 0;
381              
382             $self->child->window->show if $self->child->window;
383             }
384              
385             =head1 TODO
386              
387             =over 4
388              
389             =item *
390              
391             Support adjusting stacking order of floats.
392              
393             =back
394              
395             =cut
396              
397             =head1 AUTHOR
398              
399             Paul Evans
400              
401             =cut
402              
403             0x55AA