]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/layout/canvas_layout_test.cxx
canvas::BoxLayout: fix parent ref on add/remove.
[simgear.git] / simgear / canvas / layout / canvas_layout_test.cxx
1 // Testing canvas layouting system
2 //
3 // Copyright (C) 2014  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
9 //
10 // This library 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 GNU
13 // Library General Public License for more details.
14 //
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
18
19 #define BOOST_TEST_MODULE canvas_layout
20 #include <BoostTestTargetConfig.h>
21
22 #include "BoxLayout.hxx"
23 #include "NasalWidget.hxx"
24
25 #include <simgear/debug/logstream.hxx>
26 #include <cstdlib>
27
28 //------------------------------------------------------------------------------
29 struct SetLogLevelFixture
30 {
31   SetLogLevelFixture()
32   {
33     sglog().set_log_priority(SG_DEBUG);
34   }
35 };
36 BOOST_GLOBAL_FIXTURE(SetLogLevelFixture);
37
38 //------------------------------------------------------------------------------
39 namespace sc = simgear::canvas;
40
41 class TestWidget:
42   public sc::LayoutItem
43 {
44   public:
45     TestWidget( const SGVec2i& min_size,
46                 const SGVec2i& size_hint,
47                 const SGVec2i& max_size = MAX_SIZE )
48     {
49       _size_hint = size_hint;
50       _min_size = min_size;
51       _max_size = max_size;
52     }
53
54     TestWidget(const TestWidget& rhs)
55     {
56       _size_hint = rhs._size_hint;
57       _min_size = rhs._min_size;
58       _max_size = rhs._max_size;
59     }
60
61     void setMinSize(const SGVec2i& size) { _min_size = size; }
62     void setMaxSize(const SGVec2i& size) { _max_size = size; }
63     void setSizeHint(const SGVec2i& size) { _size_hint = size; }
64
65     virtual void setGeometry(const SGRecti& geom) { _geom = geom; }
66     virtual SGRecti geometry() const { return _geom; }
67
68   protected:
69
70     SGRecti _geom;
71
72     virtual SGVec2i sizeHintImpl() const { return _size_hint; }
73     virtual SGVec2i minimumSizeImpl() const { return _min_size; }
74     virtual SGVec2i maximumSizeImpl() const { return _max_size; }
75 };
76
77 class TestWidgetHFW:
78   public TestWidget
79 {
80   public:
81     TestWidgetHFW( const SGVec2i& min_size,
82                    const SGVec2i& size_hint,
83                    const SGVec2i& max_size = MAX_SIZE ):
84       TestWidget(min_size, size_hint, max_size)
85     {
86
87     }
88
89     virtual bool hasHeightForWidth() const
90     {
91       return true;
92     }
93
94     virtual int heightForWidth(int w) const
95     {
96       return _size_hint.x() * _size_hint.y() / w;
97     }
98
99     virtual int minimumHeightForWidth(int w) const
100     {
101       return _min_size.x() * _min_size.y() / w;
102     }
103 };
104
105 typedef SGSharedPtr<TestWidget> TestWidgetRef;
106
107 //------------------------------------------------------------------------------
108 BOOST_AUTO_TEST_CASE( horizontal_layout )
109 {
110   sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop);
111   box_layout.setSpacing(5);
112
113   BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::BottomToTop);
114   BOOST_CHECK_EQUAL(box_layout.spacing(), 5);
115
116   box_layout.setDirection(sc::BoxLayout::LeftToRight);
117   box_layout.setSpacing(9);
118
119   BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::LeftToRight);
120   BOOST_CHECK_EQUAL(box_layout.spacing(), 9);
121
122   TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
123                                                    SGVec2i(16, 16),
124                                                    SGVec2i(16, 16) ) );
125   box_layout.addItem(fixed_size_widget);
126
127   BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(16, 16));
128   BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(16, 16));
129   BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(16, 16));
130
131   TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
132                                                        SGVec2i(32, 32),
133                                                        SGVec2i(256, 64) ) );
134   box_layout.addItem(limited_resize_widget);
135
136   // Combined sizes of both widget plus the padding between them
137   BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(41, 16));
138   BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(57, 32));
139   BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(281, 64));
140
141   // Test with different spacing/padding
142   box_layout.setSpacing(5);
143
144   BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(37, 16));
145   BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(53, 32));
146   BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(277, 64));
147
148   box_layout.setGeometry(SGRecti(0, 0, 128, 32));
149
150   // Fixed size for first widget and remaining space goes to second widget
151   BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
152   BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 107, 32));
153
154   TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16),
155                                                 SGVec2i(32, 32),
156                                                 SGVec2i(128, 32) ) );
157   box_layout.addItem(stretch_widget, 1);
158   box_layout.update();
159
160   BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(58, 16));
161   BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(90, 32));
162   BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(410, 64));
163
164   // Due to the stretch factor only the last widget gets additional space. All
165   // other widgets get the preferred size.
166   BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
167   BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 32, 32));
168   BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(58, 0, 70, 32));
169
170   // Test stretch factor
171   TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) );
172   sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight);
173
174   box_layout_stretch.addItem(stretch_widget, 1);
175   box_layout_stretch.addItem(fast_stretch, 2);
176
177   box_layout_stretch.setGeometry(SGRecti(0,0,128,32));
178
179   BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 41, 32));
180   BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(46, 0, 82, 32));
181
182   box_layout_stretch.setGeometry(SGRecti(0,0,256,32));
183
184   BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 123, 32));
185   BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(128, 0, 128, 32));
186
187   // Test superflous space to padding
188   box_layout_stretch.setGeometry(SGRecti(0,0,512,32));
189
190   BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(83, 0, 128, 32));
191   BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(300, 0, 128, 32));
192
193   // Test more space then preferred, but less than maximum
194   {
195     sc::HBoxLayout hbox;
196     TestWidgetRef w1( new TestWidget( SGVec2i(16,   16),
197                                       SGVec2i(32,   32),
198                                       SGVec2i(9999, 32) ) ),
199                   w2( new TestWidget(*w1) );
200
201     hbox.addItem(w1);
202     hbox.addItem(w2);
203
204     hbox.setGeometry( SGRecti(0, 0, 256, 32) );
205
206     BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,   0, 126, 32));
207     BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(131, 0, 125, 32));
208
209     hbox.setStretch(0, 1);
210     hbox.setStretch(1, 1);
211
212     BOOST_CHECK_EQUAL(hbox.stretch(0), 1);
213     BOOST_CHECK_EQUAL(hbox.stretch(1), 1);
214
215     hbox.update();
216
217     BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,   0, 125, 32));
218     BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
219
220     BOOST_REQUIRE( hbox.setStretchFactor(w1, 2) );
221     BOOST_REQUIRE( hbox.setStretchFactor(w2, 3) );
222     BOOST_CHECK_EQUAL(hbox.stretch(0), 2);
223     BOOST_CHECK_EQUAL(hbox.stretch(1), 3);
224
225     hbox.removeItem(w1);
226
227     BOOST_CHECK( !hbox.setStretchFactor(w1, 0) );
228   }
229 }
230
231 //------------------------------------------------------------------------------
232 BOOST_AUTO_TEST_CASE( spacer_layouting )
233 {
234   sc::HBoxLayout hbox;
235   TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
236                                     SGVec2i(32, 32),
237                                     SGVec2i(9999, 9999) ) ),
238                 w2( new TestWidget(*w1) );
239
240   hbox.addItem(w1);
241   hbox.addItem(w2);
242   hbox.addStretch(1);
243
244   BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(37, 16));
245   BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(69, 32));
246   BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
247
248   hbox.setGeometry(SGRecti(0, 0, 256, 40));
249
250   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0,  0, 32, 40));
251   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(37, 0, 32, 40));
252
253   // now center with increased spacing between both widgets
254   hbox.insertStretch(0, 1);
255   hbox.insertSpacing(2, 10);
256
257   BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(47, 16));
258   BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(79, 32));
259   BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
260
261   hbox.update();
262
263   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(88,  0, 32, 40));
264   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(135, 0, 32, 40));
265 }
266
267 //------------------------------------------------------------------------------
268 BOOST_AUTO_TEST_CASE( vertical_layout)
269 {
270   sc::BoxLayout vbox(sc::BoxLayout::TopToBottom);
271   vbox.setSpacing(7);
272
273   TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
274                                                    SGVec2i(16, 16),
275                                                    SGVec2i(16, 16) ) );
276   TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
277                                                        SGVec2i(32, 32),
278                                                        SGVec2i(256, 64) ) );
279
280   vbox.addItem(fixed_size_widget);
281   vbox.addItem(limited_resize_widget);
282
283   BOOST_CHECK_EQUAL(vbox.minimumSize(), SGVec2i(16, 39));
284   BOOST_CHECK_EQUAL(vbox.sizeHint(), SGVec2i(32, 55));
285   BOOST_CHECK_EQUAL(vbox.maximumSize(), SGVec2i(256, 87));
286
287   vbox.setGeometry(SGRecti(10, 20, 16, 55));
288
289   BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(10, 20, 16, 16));
290   BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(10, 43, 16, 32));
291
292   vbox.setDirection(sc::BoxLayout::BottomToTop);
293 }
294
295 //------------------------------------------------------------------------------
296 BOOST_AUTO_TEST_CASE( boxlayout_insert_remove )
297 {
298   sc::BoxLayoutRef hbox( new sc::HBoxLayout );
299
300   BOOST_CHECK_EQUAL(hbox->count(), 0);
301   BOOST_CHECK(!hbox->itemAt(0));
302   BOOST_CHECK(!hbox->takeAt(0));
303
304   TestWidgetRef w1( new TestWidget( SGVec2i(16,   16),
305                                     SGVec2i(32,   32),
306                                     SGVec2i(9999, 32) ) ),
307                 w2( new TestWidget(*w1) );
308
309   hbox->addItem(w1);
310   BOOST_CHECK_EQUAL(hbox->count(), 1);
311   BOOST_CHECK_EQUAL(hbox->itemAt(0), w1);
312   BOOST_CHECK_EQUAL(w1->getParent(), hbox);
313
314   hbox->insertItem(0, w2);
315   BOOST_CHECK_EQUAL(hbox->count(), 2);
316   BOOST_CHECK_EQUAL(hbox->itemAt(0), w2);
317   BOOST_CHECK_EQUAL(hbox->itemAt(1), w1);
318   BOOST_CHECK_EQUAL(w2->getParent(), hbox);
319
320   hbox->removeItem(w2);
321   BOOST_CHECK_EQUAL(hbox->count(), 1);
322   BOOST_CHECK_EQUAL(hbox->itemAt(0), w1);
323   BOOST_CHECK( !w2->getParent() );
324
325   hbox->addItem(w2);
326   BOOST_CHECK_EQUAL(hbox->count(), 2);
327   BOOST_CHECK_EQUAL(w2->getParent(), hbox);
328
329   hbox->clear();
330   BOOST_CHECK_EQUAL(hbox->count(), 0);
331   BOOST_CHECK( !w1->getParent() );
332   BOOST_CHECK( !w2->getParent() );
333 }
334
335 //------------------------------------------------------------------------------
336 BOOST_AUTO_TEST_CASE( boxlayout_hfw )
337 {
338   TestWidgetRef w1( new TestWidgetHFW( SGVec2i(16,   16),
339                                        SGVec2i(32,   32) ) ),
340                 w2( new TestWidgetHFW( SGVec2i(24,   24),
341                                        SGVec2i(48,   48) ) );
342
343   BOOST_CHECK_EQUAL(w1->heightForWidth(16), 64);
344   BOOST_CHECK_EQUAL(w1->minimumHeightForWidth(16), 16);
345   BOOST_CHECK_EQUAL(w2->heightForWidth(24), 96);
346   BOOST_CHECK_EQUAL(w2->minimumHeightForWidth(24), 24);
347
348   TestWidgetRef w_no_hfw( new TestWidget( SGVec2i(16,   16),
349                                           SGVec2i(32,   32) ) );
350   BOOST_CHECK(!w_no_hfw->hasHeightForWidth());
351   BOOST_CHECK_EQUAL(w_no_hfw->heightForWidth(16), -1);
352   BOOST_CHECK_EQUAL(w_no_hfw->minimumHeightForWidth(16), -1);
353
354   // horizontal
355   sc::HBoxLayout hbox;
356   hbox.setSpacing(5);
357   hbox.addItem(w1);
358   hbox.addItem(w2);
359
360   BOOST_CHECK_EQUAL(hbox.heightForWidth(45), w2->heightForWidth(24));
361   BOOST_CHECK_EQUAL(hbox.heightForWidth(85), w2->heightForWidth(48));
362
363   hbox.addItem(w_no_hfw);
364
365   BOOST_CHECK_EQUAL(hbox.heightForWidth(66), 96);
366   BOOST_CHECK_EQUAL(hbox.heightForWidth(122), 48);
367   BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(66), 24);
368   BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(122), 16);
369
370   hbox.setGeometry(SGRecti(0, 0, 66, 24));
371
372   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0,  16, 24));
373   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(21, 0, 24, 24));
374   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(50, 0, 16, 24));
375
376   // vertical
377   sc::VBoxLayout vbox;
378   vbox.setSpacing(5);
379   vbox.addItem(w1);
380   vbox.addItem(w2);
381
382   BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 143);
383   BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 74);
384   BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 39);
385   BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 22);
386
387   vbox.addItem(w_no_hfw);
388
389   BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 180);
390   BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 111);
391   BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 60);
392   BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 43);
393
394   SGVec2i min_size = vbox.minimumSize(),
395           size_hint = vbox.sizeHint();
396
397   BOOST_CHECK_EQUAL(min_size, SGVec2i(24, 66));
398   BOOST_CHECK_EQUAL(size_hint, SGVec2i(48, 122));
399
400   vbox.setGeometry(SGRecti(0, 0, 24, 122));
401
402   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0,  24, 33));
403   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47));
404   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
405
406   // Vertical layouting modifies size hints, so check if they are correctly
407   // restored
408   BOOST_CHECK_EQUAL(min_size, vbox.minimumSize());
409   BOOST_CHECK_EQUAL(size_hint, vbox.sizeHint());
410
411   vbox.setGeometry(SGRecti(0, 0, 50, 122));
412
413   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0,  50, 25));
414   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 30, 50, 51));
415   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 86, 50, 36));
416
417   // Same geometry as before -> should get same widget geometry
418   // (check internal size hint cache updates correctly)
419   vbox.setGeometry(SGRecti(0, 0, 24, 122));
420
421   BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0,  24, 33));
422   BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47));
423   BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
424 }
425
426 //------------------------------------------------------------------------------
427 BOOST_AUTO_TEST_CASE( nasal_widget )
428 {
429   naContext c = naNewContext();
430   naRef me = naNewHash(c);
431
432   sc::NasalWidgetRef w( new sc::NasalWidget(me) );
433
434   // Default layout sizes (no user set values)
435   BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(16, 16));
436   BOOST_CHECK_EQUAL(w->sizeHint(),    SGVec2i(32, 32));
437   BOOST_CHECK_EQUAL(w->maximumSize(), sc::LayoutItem::MAX_SIZE);
438
439   // Changed layout sizes
440   w->setLayoutMinimumSize( SGVec2i(2, 12) );
441   w->setLayoutSizeHint(    SGVec2i(3, 13) );
442   w->setLayoutMaximumSize( SGVec2i(4, 14) );
443
444   BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 12));
445   BOOST_CHECK_EQUAL(w->sizeHint(),    SGVec2i(3, 13));
446   BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 14));
447
448   // User set values (overwrite layout sizes)
449   w->setMinimumSize( SGVec2i(15, 16) );
450   w->setSizeHint(    SGVec2i(17, 18) );
451   w->setMaximumSize( SGVec2i(19, 20) );
452
453   BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(15, 16));
454   BOOST_CHECK_EQUAL(w->sizeHint(),    SGVec2i(17, 18));
455   BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(19, 20));
456
457   // Only vertical user set values (layout/default for horizontal hints)
458   w->setMinimumSize( SGVec2i(0, 21) );
459   w->setSizeHint(    SGVec2i(0, 22) );
460   w->setMaximumSize( SGVec2i(SGLimits<int>::max(), 23) );
461
462   BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 21));
463   BOOST_CHECK_EQUAL(w->sizeHint(),    SGVec2i(3, 22));
464   BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 23));
465
466   naFreeContext(c);
467 }