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