add new Box container for Canvas.
[ardour.git] / libs / canvas / box.cc
1 /*
2     Copyright (C) 2011-2014 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 <algorithm>
21
22 #include "canvas/box.h"
23 #include "canvas/rectangle.h"
24
25 using namespace ArdourCanvas;
26
27 Box::Box (Canvas* canvas, Orientation o)
28         : Item (canvas)
29         , orientation (o)
30         , spacing (0)
31         , top_padding (0), right_padding (0), bottom_padding (0), left_padding (0)
32         , top_margin (0), right_margin (0), bottom_margin (0), left_margin (0)
33         , homogenous (false)
34 {
35         self = new Rectangle (this);
36         self->set_outline (false);
37         self->set_fill (false);
38 }
39
40 Box::Box (Item* parent, Orientation o)
41         : Item (parent)
42         , orientation (o)
43         , spacing (0)
44         , top_padding (0), right_padding (0), bottom_padding (0), left_padding (0)
45         , top_margin (0), right_margin (0), bottom_margin (0), left_margin (0)
46 {
47         self = new Rectangle (this);
48         self->set_outline (false);
49         self->set_fill (false);
50 }
51
52
53 Box::Box (Item* parent, Duple const & p, Orientation o)
54         : Item (parent, p)
55         , orientation (o)
56         , spacing (0)
57         , top_padding (0), right_padding (0), bottom_padding (0), left_padding (0)
58         , top_margin (0), right_margin (0), bottom_margin (0), left_margin (0)
59 {
60         self = new Rectangle (this);
61         self->set_outline (false);
62         self->set_fill (false);
63 }
64
65 void
66 Box::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
67 {
68         Item::render_children (area, context);
69 }
70
71 void
72 Box::compute_bounding_box () const
73 {
74         _bounding_box = boost::none;
75
76         if (_items.empty()) {
77                 _bounding_box_dirty = false;
78                 return;
79         }
80
81         add_child_bounding_boxes (!collapse_on_hide);
82
83         if (_bounding_box) {
84                 Rect r = _bounding_box.get();
85
86                 _bounding_box = r.expand (top_padding + outline_width() + top_margin,
87                                           right_padding + outline_width() + right_margin,
88                                           bottom_padding + outline_width() + bottom_margin,
89                                           left_padding + outline_width() + left_margin);
90         }
91
92         _bounding_box_dirty = false;
93 }
94
95 void
96 Box::set_spacing (double s)
97 {
98         spacing = s;
99 }
100
101 void
102 Box::set_padding (double t, double r, double b, double l)
103 {
104         double last = t;
105
106         top_padding = t;
107
108         if (r >= 0) {
109                 last = r;
110         }
111         right_padding = last;
112         if (b >= 0) {
113                 last = b;
114         }
115         bottom_padding = last;
116         if (l >= 0) {
117                 last = l;
118         }
119         left_padding = last;
120 }
121
122 void
123 Box::set_margin (double t, double r, double b, double l)
124 {
125         double last = t;
126         top_margin = t;
127         if (r >= 0) {
128                 last = r;
129         }
130         right_margin = last;
131         if (b >= 0) {
132                 last = b;
133         }
134         bottom_margin = last;
135         if (l >= 0) {
136                 last = l;
137         }
138         left_margin = last;
139 }
140
141 void
142 Box::reset_self ()
143 {
144         if (_bounding_box_dirty) {
145                 compute_bounding_box ();
146         }
147
148         if (!_bounding_box) {
149                 self->hide ();
150                 return;
151         }
152
153         Rect r (_bounding_box.get());
154
155         /* XXX need to shrink by margin */
156
157         self->set (r);
158 }
159
160 void
161 Box::reposition_children ()
162 {
163         Duple previous_edge (0, 0);
164         Distance largest_width = 0;
165         Distance largest_height = 0;
166
167         if (homogenous) {
168
169                 for (std::list<Item*>::iterator i = _items.begin(); ++i != _items.end(); ++i) {
170                         boost::optional<Rect> bb = (*i)->bounding_box();
171                         if (bb) {
172                                 largest_height = std::max (largest_height, bb.get().height());
173                                 largest_width = std::max (largest_height, bb.get().width());
174                         }
175                 }
176         }
177
178         for (std::list<Item*>::iterator i = _items.begin(); ++i != _items.end(); ++i) {
179
180                 (*i)->set_position (previous_edge);
181
182                 if (orientation == Vertical) {
183
184                         Distance shift = 0;
185
186                         if (homogenous) {
187                                 shift = largest_height;
188                         } else {
189                                 boost::optional<Rect> bb = (*i)->bounding_box();
190
191                                 if (!(*i)->visible()) {
192                                         /* invisible child */
193                                         if (!collapse_on_hide) {
194                                                 /* still add in its size */
195                                                 if (bb) {
196                                                         shift += bb.get().height();
197                                                 }
198                                         }
199                                 } else {
200                                         if (bb) {
201                                                 shift += bb.get().height();
202                                         }
203                                 }
204                         }
205
206                         previous_edge = previous_edge.translate (Duple (0, spacing + shift));
207                 } else {
208
209                         Distance shift = 0;
210
211                         if (homogenous) {
212                                 shift = largest_width;
213                         } else {
214                                 boost::optional<Rect> bb = (*i)->bounding_box();
215
216                                 if (!(*i)->visible()) {
217                                         if (!collapse_on_hide) {
218                                                 if (bb) {
219                                                         shift += bb.get().width();
220                                                 }
221                                         }
222                                 } else {
223                                         if (bb) {
224                                                 shift += bb.get().width();
225                                         }
226                                 }
227                         }
228
229                         previous_edge = previous_edge.translate (Duple (0, spacing + shift));
230                 }
231         }
232
233         _bounding_box_dirty = true;
234         reset_self ();
235 }
236
237 void
238 Box::pack_end (Item* i, double extra_padding)
239 {
240         if (!i) {
241                 return;
242         }
243
244         /* prepend new child */
245         Item::add_front (i);
246         reposition_children ();
247 }
248 void
249 Box::pack_start (Item* i, double extra_padding)
250 {
251         if (!i) {
252                 return;
253         }
254
255         /* append new child */
256         Item::add (i);
257         reposition_children ();
258 }
259
260 void
261 Box::add (Item* i)
262 {
263         pack_start (i);
264 }
265
266 void
267 Box::child_changed ()
268 {
269         /* catch visibility and size changes */
270
271         Item::child_changed ();
272         reposition_children ();
273 }
274
275 void
276 Box::set_collapse_on_hide (bool yn)
277 {
278         if (collapse_on_hide != yn) {
279                 collapse_on_hide = yn;
280                 reposition_children ();
281         }
282 }
283
284 /*----*/
285
286 VBox::VBox (Canvas* c)
287         : Box (c, Vertical)
288 {
289 }
290 VBox::VBox (Item* i)
291         : Box (i, Vertical)
292 {
293 }
294 VBox::VBox (Item* i, Duple const & position)
295         : Box (i, position, Vertical)
296 {
297 }
298
299 HBox::HBox (Canvas* c)
300         : Box (c, Horizontal)
301 {
302 }
303 HBox::HBox (Item* i)
304         : Box (i, Horizontal)
305 {
306 }
307 HBox::HBox (Item* i, Duple const & position)
308         : Box (i, position, Horizontal)
309 {
310 }