correct drawing of rectangle borders.
[ardour.git] / libs / canvas / rectangle.cc
1 /*
2     Copyright (C) 2011-2013 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <iostream>
21 #include <cairomm/context.h>
22 #include "pbd/stacktrace.h"
23 #include "pbd/compose.h"
24
25 #include "canvas/canvas.h"
26 #include "canvas/rectangle.h"
27 #include "canvas/debug.h"
28 #include "canvas/utils.h"
29
30 using namespace std;
31 using namespace ArdourCanvas;
32
33 Rectangle::Rectangle (Canvas* c)
34         : Item (c)
35         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
36 {
37 }
38
39 Rectangle::Rectangle (Canvas* c, Rect const & rect)
40         : Item (c)
41         , _rect (rect)
42         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
43 {
44 }
45
46 Rectangle::Rectangle (Item* parent)
47         : Item (parent)
48         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
49 {
50 }
51
52 Rectangle::Rectangle (Item* parent, Rect const & rect)
53         : Item (parent)
54         , _rect (rect)
55         , _outline_what ((What) (LEFT | RIGHT | TOP | BOTTOM))
56 {
57 }
58
59 Rect
60 Rectangle::get_self_for_render () const
61 {
62         /* In general, a Rectangle will have a _position of (0,0) within its
63            parent, and its extent is actually defined by _rect. But in the
64            unusual case that _position is set to something other than (0,0),
65            we should take that into account when rendering.
66         */
67
68         return item_to_window (_rect.translate (_position), false);
69 }
70
71 void
72 Rectangle::render_self (Rect const & area, Cairo::RefPtr<Cairo::Context> context, Rect self) const
73 {
74         boost::optional<Rect> r = self.intersection (area);
75         
76         if (!r) {
77                 return;
78         }
79
80         Rect draw = r.get ();
81
82         if (_fill && !_transparent) {
83                 if (_stops.empty()) {
84                         setup_fill_context (context);
85                 } else {
86                         setup_gradient_context (context, self, Duple (draw.x0, draw.y0));
87                 }
88
89                 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
90                 context->fill ();
91         } 
92         
93         if (_outline) {
94
95                 setup_outline_context (context);
96                 
97                 /* the goal here is that if the border is 1 pixel
98                  * thick, it will precisely align with the corner
99                  * coordinates of the rectangle. So if the rectangle
100                  * has a left edge at 0 and a right edge at 10, then
101                  * the left edge must span 0..1, the right edge
102                  * must span 9..10 because the first and final pixels
103                  * to be colored are actually "at" 0.5 and 9.5 (midway
104                  * between the integer coordinates.
105                  */
106
107                 if (_outline_what == What (LEFT|RIGHT|BOTTOM|TOP)) {
108                         
109                         self = self.shrink (0.5);
110                         context->rectangle (self.x0, self.y0, self.width(), self.height());
111
112                 } else {
113
114                         if (_outline_what & LEFT) {
115                                 context->move_to (self.x0+0.5, self.y0);
116                                 context->line_to (self.x0+0.5, self.y1);
117                         }
118                         
119                         if (_outline_what & TOP) {
120                                 context->move_to (self.x0, self.y0+0.5);
121                                 context->line_to (self.x1, self.y0+0.5);
122                         }
123
124                         if (_outline_what & BOTTOM) {
125                                 context->move_to (self.x0, self.y1-0.5);
126                                 context->line_to (self.x1, self.y1-0.5);
127                         }
128                         
129                         if (_outline_what & RIGHT) {
130                                 context->move_to (self.x1-0.5, self.y0);
131                                 context->line_to (self.x1-0.5, self.y1);
132                         }
133                 }
134                 
135                 context->stroke ();
136         }
137 }
138
139 void
140 Rectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
141 {
142         render_self (area, context, get_self_for_render ());
143 }
144
145 void
146 Rectangle::compute_bounding_box () const
147 {
148         if (!_rect.empty()) {
149                 Rect r = _rect.fix ();
150
151                 /* if the outline is 1 pixel, then the actual
152                    bounding box is 0.5 pixels outside the stated
153                    corners of the rectangle.
154
155                    if the outline is 2 pixels, then the actual
156                    bounding box is 1.0 pixels outside the stated
157                    corners of the rectangle (so that the middle
158                    of the 2 pixel wide border passes through
159                    the corners, alternatively described as 1 row 
160                    of pixels outside of the corners, and 1 row
161                    inside).
162
163                    if the outline is 3 pixels, then the actual
164                    bounding box is 1.5 outside the stated corners
165                    of the rectangle (so that the middle row of
166                    pixels of the border passes through the corners).
167
168                    if the outline is 4 pixels, then the actual bounding
169                    box is 2.0 pixels outside the stated corners
170                    of the rectangle, so that the border consists
171                    of 2 pixels outside the corners and 2 pixels inside.
172
173                    hence ... the bounding box is width * 0.5 larger
174                    than the rectangle itself.
175                 */
176
177                 _bounding_box = r.expand (_outline_width * 0.5);
178         }
179
180         _bounding_box_dirty = false;
181 }
182
183 void
184 Rectangle::set (Rect const & r)
185 {
186         /* We don't update the bounding box here; it's just
187            as cheap to do it when asked.
188         */
189
190         if (r != _rect) {
191                 
192                 begin_change ();
193                 
194                 _rect = r;
195                 
196                 _bounding_box_dirty = true;
197                 end_change ();
198         }
199 }
200
201 void
202 Rectangle::set_x0 (Coord x0)
203 {
204         if (x0 != _rect.x0) {
205                 begin_change ();
206                 
207                 _rect.x0 = x0;
208                 
209                 _bounding_box_dirty = true;
210                 end_change ();
211         }
212 }
213
214 void
215 Rectangle::set_y0 (Coord y0)
216 {
217         if (y0 != _rect.y0) {
218                 begin_change ();
219                 
220                 _rect.y0 = y0;
221                 
222                 _bounding_box_dirty = true;
223                 end_change();
224         }
225 }
226
227 void
228 Rectangle::set_x1 (Coord x1)
229 {
230         if (x1 != _rect.x1) {
231                 begin_change ();
232                 
233                 _rect.x1 = x1;
234                 
235                 _bounding_box_dirty = true;
236                 end_change ();
237         }
238 }
239
240 void
241 Rectangle::set_y1 (Coord y1)
242 {
243         if (y1 != _rect.y1) {
244                 begin_change ();
245                 
246                 _rect.y1 = y1;
247                 
248                 _bounding_box_dirty = true;
249                 end_change ();
250         }
251 }
252
253 void
254 Rectangle::set_outline_what (What what)
255 {
256         if (what != _outline_what) {
257                 begin_visual_change ();
258                 _outline_what = what;
259                 end_visual_change ();
260         }
261 }
262
263
264 /*-------------------*/
265
266 void
267 TimeRectangle::compute_bounding_box () const
268 {
269         Rectangle::compute_bounding_box ();
270
271         if (_bounding_box) {
272                 Rect r = _bounding_box.get ();
273                 
274                 /* This is a TimeRectangle, so its right edge is drawn 1 pixel beyond
275                  * (larger x-axis coordinates) than a normal Rectangle.
276                  */
277                 
278                 r.x1 += 1.0; /* this should be using safe_add() */
279                 
280                 _bounding_box = r;
281         }
282 }
283
284 void 
285 TimeRectangle::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
286 {
287         Rect self = get_self_for_render ();
288         
289         /* This is a TimeRectangle, so its right edge is drawn 1 pixel beyond
290          * (larger x-axis coordinates) than a normal Rectangle.
291          */
292
293         self.x1 += 1.0; /* this should be using safe_add() */
294         render_self (area, context, self);
295 }