1 // Testing canvas layouting system
3 // Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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
19 #define BOOST_TEST_MODULE canvas_layout
20 #include <BoostTestTargetConfig.h>
22 #include "BoxLayout.hxx"
23 #include "NasalWidget.hxx"
25 #include <simgear/debug/logstream.hxx>
26 #include <simgear/nasal/cppbind/NasalContext.hxx>
30 //------------------------------------------------------------------------------
31 struct SetLogLevelFixture
35 sglog().set_log_priority(SG_DEBUG);
38 BOOST_GLOBAL_FIXTURE(SetLogLevelFixture);
40 //------------------------------------------------------------------------------
41 namespace sc = simgear::canvas;
47 TestWidget( const SGVec2i& min_size,
48 const SGVec2i& size_hint,
49 const SGVec2i& max_size = MAX_SIZE )
51 _size_hint = size_hint;
56 TestWidget(const TestWidget& rhs)
58 _size_hint = rhs._size_hint;
59 _min_size = rhs._min_size;
60 _max_size = rhs._max_size;
63 void setMinSize(const SGVec2i& size) { _min_size = size; }
64 void setMaxSize(const SGVec2i& size) { _max_size = size; }
65 void setSizeHint(const SGVec2i& size) { _size_hint = size; }
67 virtual void setGeometry(const SGRecti& geom) { _geom = geom; }
68 virtual SGRecti geometry() const { return _geom; }
74 virtual SGVec2i sizeHintImpl() const { return _size_hint; }
75 virtual SGVec2i minimumSizeImpl() const { return _min_size; }
76 virtual SGVec2i maximumSizeImpl() const { return _max_size; }
78 virtual void visibilityChanged(bool visible)
81 _geom.set(0, 0, 0, 0);
89 TestWidgetHFW( const SGVec2i& min_size,
90 const SGVec2i& size_hint,
91 const SGVec2i& max_size = MAX_SIZE ):
92 TestWidget(min_size, size_hint, max_size)
97 virtual bool hasHeightForWidth() const
102 virtual int heightForWidth(int w) const
104 return _size_hint.x() * _size_hint.y() / w;
107 virtual int minimumHeightForWidth(int w) const
109 return _min_size.x() * _min_size.y() / w;
113 typedef SGSharedPtr<TestWidget> TestWidgetRef;
115 //------------------------------------------------------------------------------
116 BOOST_AUTO_TEST_CASE( horizontal_layout )
118 sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop);
119 box_layout.setSpacing(5);
121 BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::BottomToTop);
122 BOOST_CHECK_EQUAL(box_layout.spacing(), 5);
124 box_layout.setDirection(sc::BoxLayout::LeftToRight);
125 box_layout.setSpacing(9);
127 BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::LeftToRight);
128 BOOST_CHECK_EQUAL(box_layout.spacing(), 9);
130 TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
133 box_layout.addItem(fixed_size_widget);
135 BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(16, 16));
136 BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(16, 16));
137 BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(16, 16));
139 TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
141 SGVec2i(256, 64) ) );
142 box_layout.addItem(limited_resize_widget);
144 // Combined sizes of both widget plus the padding between them
145 BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(41, 16));
146 BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(57, 32));
147 BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(281, 64));
149 // Test with different spacing/padding
150 box_layout.setSpacing(5);
152 BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(37, 16));
153 BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(53, 32));
154 BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(277, 64));
156 box_layout.setGeometry(SGRecti(0, 0, 128, 32));
158 // Fixed size for first widget and remaining space goes to second widget
159 BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
160 BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 107, 32));
162 TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16),
164 SGVec2i(128, 32) ) );
165 box_layout.addItem(stretch_widget, 1);
168 BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(58, 16));
169 BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(90, 32));
170 BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(410, 64));
172 // Due to the stretch factor only the last widget gets additional space. All
173 // other widgets get the preferred size.
174 BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
175 BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 32, 32));
176 BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(58, 0, 70, 32));
178 // Test stretch factor
179 TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) );
180 sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight);
182 box_layout_stretch.addItem(stretch_widget, 1);
183 box_layout_stretch.addItem(fast_stretch, 2);
185 box_layout_stretch.setGeometry(SGRecti(0,0,128,32));
187 BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 41, 32));
188 BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(46, 0, 82, 32));
190 box_layout_stretch.setGeometry(SGRecti(0,0,256,32));
192 BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 123, 32));
193 BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(128, 0, 128, 32));
195 // Test superflous space to padding
196 box_layout_stretch.setGeometry(SGRecti(0,0,512,32));
198 BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(83, 0, 128, 32));
199 BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(300, 0, 128, 32));
201 // Test more space then preferred, but less than maximum
204 TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
206 SGVec2i(9999, 32) ) ),
207 w2( new TestWidget(*w1) );
212 hbox.setGeometry( SGRecti(0, 0, 256, 32) );
214 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 126, 32));
215 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(131, 0, 125, 32));
217 hbox.setStretch(0, 1);
218 hbox.setStretch(1, 1);
220 BOOST_CHECK_EQUAL(hbox.stretch(0), 1);
221 BOOST_CHECK_EQUAL(hbox.stretch(1), 1);
225 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 125, 32));
226 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
228 BOOST_REQUIRE( hbox.setStretchFactor(w1, 2) );
229 BOOST_REQUIRE( hbox.setStretchFactor(w2, 3) );
230 BOOST_CHECK_EQUAL(hbox.stretch(0), 2);
231 BOOST_CHECK_EQUAL(hbox.stretch(1), 3);
235 BOOST_CHECK( !hbox.setStretchFactor(w1, 0) );
239 //------------------------------------------------------------------------------
240 BOOST_AUTO_TEST_CASE( spacer_layouting )
243 TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
245 SGVec2i(9999, 9999) ) ),
246 w2( new TestWidget(*w1) );
252 BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(37, 16));
253 BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(69, 32));
254 BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
256 hbox.setGeometry(SGRecti(0, 0, 256, 40));
258 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 32, 40));
259 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(37, 0, 32, 40));
261 // now center with increased spacing between both widgets
262 hbox.insertStretch(0, 1);
263 hbox.insertSpacing(2, 10);
265 BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(47, 16));
266 BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(79, 32));
267 BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
271 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(88, 0, 32, 40));
272 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(135, 0, 32, 40));
275 //------------------------------------------------------------------------------
276 BOOST_AUTO_TEST_CASE( vertical_layout)
278 sc::BoxLayout vbox(sc::BoxLayout::TopToBottom);
281 TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
284 TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
286 SGVec2i(256, 64) ) );
288 vbox.addItem(fixed_size_widget);
289 vbox.addItem(limited_resize_widget);
291 BOOST_CHECK_EQUAL(vbox.minimumSize(), SGVec2i(16, 39));
292 BOOST_CHECK_EQUAL(vbox.sizeHint(), SGVec2i(32, 55));
293 BOOST_CHECK_EQUAL(vbox.maximumSize(), SGVec2i(256, 87));
295 vbox.setGeometry(SGRecti(10, 20, 16, 55));
297 BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(10, 20, 16, 16));
298 BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(10, 43, 16, 32));
300 vbox.setDirection(sc::BoxLayout::BottomToTop);
303 //------------------------------------------------------------------------------
304 BOOST_AUTO_TEST_CASE( boxlayout_insert_remove )
306 sc::BoxLayoutRef hbox( new sc::HBoxLayout );
308 BOOST_CHECK_EQUAL(hbox->count(), 0);
309 BOOST_CHECK(!hbox->itemAt(0));
310 BOOST_CHECK(!hbox->takeAt(0));
312 TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
314 SGVec2i(9999, 32) ) ),
315 w2( new TestWidget(*w1) );
318 BOOST_CHECK_EQUAL(hbox->count(), 1);
319 BOOST_CHECK_EQUAL(hbox->itemAt(0), w1);
320 BOOST_CHECK_EQUAL(w1->getParent(), hbox);
322 hbox->insertItem(0, w2);
323 BOOST_CHECK_EQUAL(hbox->count(), 2);
324 BOOST_CHECK_EQUAL(hbox->itemAt(0), w2);
325 BOOST_CHECK_EQUAL(hbox->itemAt(1), w1);
326 BOOST_CHECK_EQUAL(w2->getParent(), hbox);
328 hbox->removeItem(w2);
329 BOOST_CHECK_EQUAL(hbox->count(), 1);
330 BOOST_CHECK_EQUAL(hbox->itemAt(0), w1);
331 BOOST_CHECK( !w2->getParent() );
334 BOOST_CHECK_EQUAL(hbox->count(), 2);
335 BOOST_CHECK_EQUAL(w2->getParent(), hbox);
338 BOOST_CHECK_EQUAL(hbox->count(), 0);
339 BOOST_CHECK( !w1->getParent() );
340 BOOST_CHECK( !w2->getParent() );
343 //------------------------------------------------------------------------------
344 BOOST_AUTO_TEST_CASE( boxlayout_visibility )
346 sc::BoxLayoutRef hbox( new sc::HBoxLayout );
347 TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
349 w2( new TestWidget(*w1) ),
350 w3( new TestWidget(*w1) );
356 BOOST_REQUIRE_EQUAL(hbox->sizeHint().x(), 3 * 32 + 2 * hbox->spacing());
358 hbox->setGeometry(SGRecti(0, 0, 69, 32));
360 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 20, 32));
361 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(25, 0, 20, 32));
362 BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(50, 0, 19, 32));
364 w2->setVisible(false);
366 BOOST_REQUIRE(hbox->isVisible());
367 BOOST_REQUIRE(w1->isVisible());
368 BOOST_REQUIRE(!w2->isVisible());
369 BOOST_REQUIRE(w2->isExplicitlyHidden());
370 BOOST_REQUIRE(w3->isVisible());
372 BOOST_CHECK_EQUAL(hbox->sizeHint().x(), 2 * 32 + 1 * hbox->spacing());
376 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 32, 32));
377 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 0, 0, 0));
378 BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(37, 0, 32, 32));
380 hbox->setVisible(false);
382 BOOST_REQUIRE(!hbox->isVisible());
383 BOOST_REQUIRE(hbox->isExplicitlyHidden());
384 BOOST_REQUIRE(!w1->isVisible());
385 BOOST_REQUIRE(!w1->isExplicitlyHidden());
386 BOOST_REQUIRE(!w2->isVisible());
387 BOOST_REQUIRE(w2->isExplicitlyHidden());
388 BOOST_REQUIRE(!w3->isVisible());
389 BOOST_REQUIRE(!w3->isExplicitlyHidden());
391 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 0, 0));
392 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 0, 0, 0));
393 BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(0, 0, 0, 0));
395 w2->setVisible(true);
397 BOOST_REQUIRE(!w2->isVisible());
398 BOOST_REQUIRE(!w2->isExplicitlyHidden());
400 hbox->setVisible(true);
402 BOOST_REQUIRE(hbox->isVisible());
403 BOOST_REQUIRE(w1->isVisible());
404 BOOST_REQUIRE(w2->isVisible());
405 BOOST_REQUIRE(w3->isVisible());
409 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 20, 32));
410 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(25, 0, 20, 32));
411 BOOST_CHECK_EQUAL(w3->geometry(), SGRecti(50, 0, 19, 32));
414 //------------------------------------------------------------------------------
415 BOOST_AUTO_TEST_CASE( boxlayout_hfw )
417 TestWidgetRef w1( new TestWidgetHFW( SGVec2i(16, 16),
419 w2( new TestWidgetHFW( SGVec2i(24, 24),
422 BOOST_CHECK_EQUAL(w1->heightForWidth(16), 64);
423 BOOST_CHECK_EQUAL(w1->minimumHeightForWidth(16), 16);
424 BOOST_CHECK_EQUAL(w2->heightForWidth(24), 96);
425 BOOST_CHECK_EQUAL(w2->minimumHeightForWidth(24), 24);
427 TestWidgetRef w_no_hfw( new TestWidget( SGVec2i(16, 16),
429 BOOST_CHECK(!w_no_hfw->hasHeightForWidth());
430 BOOST_CHECK_EQUAL(w_no_hfw->heightForWidth(16), -1);
431 BOOST_CHECK_EQUAL(w_no_hfw->minimumHeightForWidth(16), -1);
439 BOOST_CHECK_EQUAL(hbox.heightForWidth(45), w2->heightForWidth(24));
440 BOOST_CHECK_EQUAL(hbox.heightForWidth(85), w2->heightForWidth(48));
442 hbox.addItem(w_no_hfw);
444 BOOST_CHECK_EQUAL(hbox.heightForWidth(66), 96);
445 BOOST_CHECK_EQUAL(hbox.heightForWidth(122), 48);
446 BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(66), 24);
447 BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(122), 16);
449 hbox.setGeometry(SGRecti(0, 0, 66, 24));
451 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 16, 24));
452 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(21, 0, 24, 24));
453 BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(50, 0, 16, 24));
461 BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 143);
462 BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 74);
463 BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 39);
464 BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 22);
466 vbox.addItem(w_no_hfw);
468 BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 180);
469 BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 111);
470 BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 60);
471 BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 43);
473 SGVec2i min_size = vbox.minimumSize(),
474 size_hint = vbox.sizeHint();
476 BOOST_CHECK_EQUAL(min_size, SGVec2i(24, 66));
477 BOOST_CHECK_EQUAL(size_hint, SGVec2i(48, 122));
479 vbox.setGeometry(SGRecti(0, 0, 24, 122));
481 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 24, 33));
482 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47));
483 BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
485 // Vertical layouting modifies size hints, so check if they are correctly
487 BOOST_CHECK_EQUAL(min_size, vbox.minimumSize());
488 BOOST_CHECK_EQUAL(size_hint, vbox.sizeHint());
490 vbox.setGeometry(SGRecti(0, 0, 50, 122));
492 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 50, 25));
493 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 30, 50, 51));
494 BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 86, 50, 36));
496 // Same geometry as before -> should get same widget geometry
497 // (check internal size hint cache updates correctly)
498 vbox.setGeometry(SGRecti(0, 0, 24, 122));
500 BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 24, 33));
501 BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47));
502 BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
505 // TODO extend to_nasal_helper for automatic argument conversion
506 static naRef f_Widget_visibilityChanged(nasal::CallContext ctx)
508 sc::NasalWidget* w = ctx.from_nasal<sc::NasalWidget*>(ctx.me);
510 if( !ctx.requireArg<bool>(0) )
511 w->setGeometry(SGRecti(0, 0, -1, -1));
516 //------------------------------------------------------------------------------
517 BOOST_AUTO_TEST_CASE( nasal_widget )
520 nasal::Hash globals = c.newHash();
522 nasal::Object::setupGhost();
523 nasal::Ghost<sc::LayoutItemRef>::init("LayoutItem");
524 sc::NasalWidget::setupGhost(globals);
526 nasal::Hash me = c.newHash();
527 me.set("visibilityChanged", &f_Widget_visibilityChanged);
528 sc::NasalWidgetRef w( new sc::NasalWidget(me.get_naRef()) );
530 // Default layout sizes (no user set values)
531 BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(16, 16));
532 BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(32, 32));
533 BOOST_CHECK_EQUAL(w->maximumSize(), sc::LayoutItem::MAX_SIZE);
535 // Changed layout sizes
536 w->setLayoutMinimumSize( SGVec2i(2, 12) );
537 w->setLayoutSizeHint( SGVec2i(3, 13) );
538 w->setLayoutMaximumSize( SGVec2i(4, 14) );
540 BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 12));
541 BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(3, 13));
542 BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 14));
544 // User set values (overwrite layout sizes)
545 w->setMinimumSize( SGVec2i(15, 16) );
546 w->setSizeHint( SGVec2i(17, 18) );
547 w->setMaximumSize( SGVec2i(19, 20) );
549 BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(15, 16));
550 BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(17, 18));
551 BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(19, 20));
553 // Only vertical user set values (layout/default for horizontal hints)
554 w->setMinimumSize( SGVec2i(0, 21) );
555 w->setSizeHint( SGVec2i(0, 22) );
556 w->setMaximumSize( SGVec2i(SGLimits<int>::max(), 23) );
558 BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 21));
559 BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(3, 22));
560 BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 23));
562 w->setVisible(false);
563 BOOST_CHECK_EQUAL(w->geometry(), SGRecti(0, 0, -1, -1));