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, 2011-2016 -- leonerd@leonerd.org.uk
5
6package Tickit::Rect 0.72;
7
8use v5.14;
9use warnings;
10
11use Carp;
12
13# Load the XS code
14require Tickit;
15
16=head1 NAME
17
18C<Tickit::Rect> - a lightweight data structure representing a rectangle
19
20=head1 SYNOPSIS
21
22 use Tickit::Rect;
23
24 my $rect = Tickit::Rect->new(
25    top => 0, left => 5, lines => 3, cols => 10
26 );
27
28=head1 DESCRIPTION
29
30Objects in this class represent a rectangle, by storing the top left corner
31coordinate and the size in lines and columns. This data structure is purely
32abstract and not tied to a particular window or coordinate system. It exists
33simply as a convenient data store containing some useful utility methods.
34
35=cut
36
37=head1 CONSTRUCTORS
38
39=cut
40
41=head2 new
42
43   $rect = Tickit::Rect->new( %args )
44
45Construct a new rectangle of the given geometry, given by C<top>, C<left> and
46either C<lines> and C<cols>, or C<bottom> and C<right>.
47
48   $rect = Tickit::Rect->new( $str )
49
50If given a single string, this will be parsed in the form
51
52 (left,top)..(right,bottom)
53
54=cut
55
56sub new
57{
58   my $class = shift;
59   my %args;
60   if( @_ == 1 ) {
61      @args{qw(left top right bottom)} =
62         $_[0] =~ m/^\((\d+),(\d+)\)..\((\d+),(\d+)\)$/ or croak "Unrecognised Tickit::Rect string '$_[0]'";
63   }
64   else {
65      %args = @_;
66   }
67
68   defined $args{lines} or $args{lines} = $args{bottom} - $args{top};
69   defined $args{cols}  or $args{cols}  = $args{right}  - $args{left};
70
71   return $class->_new( @args{qw( top left lines cols )} );
72}
73
74=head2 intersect
75
76   $rect = $existing_rect->intersect( $other_rect )
77
78If there is an intersection between the given rectangles, return it. If not,
79return C<undef>.
80
81=cut
82
83=head2 translate
84
85   $rect = $existing_rect->translate( $downward, $rightward )
86
87Returns a new rectangle of the same size as the given one, moved down and to
88the right by the given argmuents (which may be negative)
89
90=cut
91
92=head1 ACCESSORS
93
94=cut
95
96=head2 top
97
98=head2 left
99
100=head2 bottom
101
102=head2 right
103
104   $top = $rect->top
105
106   $left = $rect->left
107
108   $bottom = $rect->bottom
109
110   $right = $rect->right
111
112Return the edge boundaries of the rectangle.
113
114=head2 lines
115
116=head2 cols
117
118   $lines = $rect->lines
119
120   $cols = $rect->cols
121
122Return the size of the rectangle.
123
124=cut
125
126=head2 linerange
127
128   @lines = $rect->linerange( $min, $max )
129
130A convenient shortcut to generate the list of lines covered that are within
131the given bounds (either bound may be given as C<undef>). Without bounds,
132equivalent to:
133
134   $rect->top .. $rect->bottom - 1
135
136=cut
137
138sub linerange
139{
140   my $self = shift;
141   my ( $min, $max ) = @_;
142
143   my $start = $self->top;
144   $start = $min if defined $min and $min > $start;
145
146   my $stop = $self->bottom - 1;
147   $stop = $max if defined $max and $max < $stop;
148
149   return $start .. $stop;
150}
151
152=head1 METHODS
153
154=cut
155
156=head2 equals
157
158   $bool = $rect->equals( $other )
159
160   $bool = ( $rect == $other )
161
162Returns true if C<$other> represents the same area as C<$rect>. This method
163overloads the numerical equality operator (C<==>).
164
165=cut
166
167use overload '==' => "equals", eq => "equals";
168
169=head2 contains
170
171   $bool = $rect->contains( $other )
172
173Returns true if C<$other> is entirely contained within the bounds of C<$rect>.
174
175=cut
176
177=head2 intersects
178
179   $bool = $rect->intersects( $other )
180
181Returns true if C<$other> and C<$rect> intersect at all, even if they overlap.
182
183=cut
184
185sub sprintf
186{
187   my $self = shift;
188   return sprintf "[(%d,%d)..(%d,%d)]", $self->left, $self->top, $self->right, $self->bottom;
189}
190
191use overload
192   '""' => sub {
193      my $self = shift;
194      return ref($self) . $self->sprintf;
195   },
196   bool => sub { 1 };
197
198=head2 add
199
200   @r = $rect->add( $other )
201
202Returns a list of the non-overlapping regions covered by either C<$rect> or
203C<$other>.
204
205In the trivial case that the two given rectangles do not touch, the result
206will simply be a list of the two initial rectangles. Otherwise a list of
207newly-constructed rectangles will be returned that covers the same area as
208the original two. This list will contain anywhere between 1 and 3 rectangles.
209
210=cut
211
212=head2 subtract
213
214   @r = $rect->subtract( $other )
215
216Returns a list of the non-overlapping regions covered by C<$rect> but not by
217C<$other>.
218
219In the trivial case that C<$other> completely covers C<$rect> then the empty
220list is returned. In the trivial case that C<$other> and C<$rect> do not
221intersect then a list containing C<$rect> is returned. Otherwise, a list of
222newly-constructed rectangles will be returned that covers the required area.
223This list will contain anywhere between 1 and 4 rectangles.
224
225=cut
226
227=head1 AUTHOR
228
229Paul Evans <leonerd@leonerd.org.uk>
230
231=cut
232
2330x55AA;
234