canvas::Layout: support height-for-width layouting.

This commit is contained in:
Thomas Geymayer 2014-06-13 00:03:40 +02:00
parent e3f0fad272
commit d3b211e787
11 changed files with 362 additions and 4 deletions

View File

@ -175,6 +175,35 @@ namespace canvas
return _direction;
}
//----------------------------------------------------------------------------
bool BoxLayout::hasHeightForWidth() const
{
if( _flags & SIZE_INFO_DIRTY )
updateSizeHints();
return _layout_data.has_hfw;
}
//----------------------------------------------------------------------------
int BoxLayout::heightForWidth(int w) const
{
if( !hasHeightForWidth() )
return -1;
updateWFHCache(w);
return _hfw_height;
}
//----------------------------------------------------------------------------
int BoxLayout::minimumHeightForWidth(int w) const
{
if( !hasHeightForWidth() )
return -1;
updateWFHCache(w);
return _hfw_min_height;
}
//----------------------------------------------------------------------------
void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
{
@ -198,6 +227,8 @@ namespace canvas
size_hint(0, 0);
_layout_data.reset();
_hfw_width = _hfw_height = _hfw_min_height = -1;
bool is_first = true;
for(size_t i = 0; i < _layout_items.size(); ++i)
@ -210,6 +241,7 @@ namespace canvas
item_data.min_size = (item.minimumSize().*_get_layout_coord)();
item_data.max_size = (item.maximumSize().*_get_layout_coord)();
item_data.size_hint = (item.sizeHint().*_get_layout_coord)();
item_data.has_hfw = item.hasHeightForWidth();
if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
{
@ -237,6 +269,8 @@ namespace canvas
(item.maximumSize().*_get_fixed_coord)() );
size_hint.y() = std::max( size_hint.y(),
(item.sizeHint().*_get_fixed_coord)() );
_layout_data.has_hfw = _layout_data.has_hfw || item.hasHeightForWidth();
}
safeAdd(min_size.x(), _layout_data.padding);
@ -258,6 +292,40 @@ namespace canvas
_flags &= ~SIZE_INFO_DIRTY;
}
//----------------------------------------------------------------------------
void BoxLayout::updateWFHCache(int w) const
{
if( w == _hfw_width )
return;
_hfw_height = 0;
_hfw_min_height = 0;
if( horiz() )
{
_layout_data.size = w;
const_cast<BoxLayout*>(this)->distribute(_layout_items, _layout_data);
for(size_t i = 0; i < _layout_items.size(); ++i)
{
ItemData const& data = _layout_items[i];
_hfw_height = std::max(_hfw_height, data.hfw(data.size));
_hfw_min_height = std::max(_hfw_min_height, data.mhfw(data.size));
}
}
else
{
for(size_t i = 0; i < _layout_items.size(); ++i)
{
ItemData const& data = _layout_items[i];
_hfw_height += data.hfw(w) + data.padding_orig;
_hfw_min_height += data.mhfw(w) + data.padding_orig;
}
}
_hfw_width = w;
}
//----------------------------------------------------------------------------
SGVec2i BoxLayout::sizeHintImpl() const
{
@ -285,9 +353,45 @@ namespace canvas
if( _flags & SIZE_INFO_DIRTY )
updateSizeHints();
// Store current size hints because vertical layouts containing
// height-for-width items the size hints are update for the actual width of
// the layout
int min_size_save = _layout_data.min_size,
size_hint_save = _layout_data.size_hint;
_layout_data.size = (geom.size().*_get_layout_coord)();
// update width dependent data for layouting of vertical layouts
if( _layout_data.has_hfw && !horiz() )
{
for(size_t i = 0; i < _layout_items.size(); ++i)
{
ItemData& data = _layout_items[i];
if( data.has_hfw )
{
int w = SGMisc<int>::clip( geom.width(),
data.layout_item->minimumSize().x(),
data.layout_item->maximumSize().x() );
int d = data.layout_item->minimumHeightForWidth(w) - data.min_size;
data.min_size += d;
_layout_data.min_size += d;
d = data.layout_item->heightForWidth(w) - data.size_hint;
data.size_hint += d;
_layout_data.size_hint += d;
}
}
}
// now do the actual layouting
distribute(_layout_items, _layout_data);
// Restore size hints possibly changed by vertical layouting
_layout_data.min_size = min_size_save;
_layout_data.size_hint = size_hint_save;
// and finally set the layouted geometry for each item
int fixed_size = (geom.size().*_get_fixed_coord)();
SGVec2i cur_pos( (geom.pos().*_get_layout_coord)(),
(geom.pos().*_get_fixed_coord)() );
@ -296,7 +400,6 @@ namespace canvas
if( reverse )
cur_pos.x() += (geom.size().*_get_layout_coord)();
// TODO handle reverse layouting (rtl/btt)
for(size_t i = 0; i < _layout_items.size(); ++i)
{
ItemData const& data = _layout_items[i];

View File

@ -75,6 +75,10 @@ namespace canvas
void setDirection(Direction dir);
Direction direction() const;
virtual bool hasHeightForWidth() const;
virtual int heightForWidth(int w) const;
virtual int minimumHeightForWidth(int w) const;
virtual void setCanvas(const CanvasWeakPtr& canvas);
bool horiz() const;
@ -95,7 +99,13 @@ namespace canvas
mutable LayoutItems _layout_items;
mutable ItemData _layout_data;
// Cache for last height-for-width query
mutable int _hfw_width,
_hfw_height,
_hfw_min_height;
void updateSizeHints() const;
void updateWFHCache(int w) const;
virtual SGVec2i sizeHintImpl() const;
virtual SGVec2i minimumSizeImpl() const;

View File

@ -78,9 +78,28 @@ namespace canvas
padding = 0;
size = 0;
stretch = 0;
has_hfw = false;
done = false;
}
//----------------------------------------------------------------------------
int Layout::ItemData::hfw(int w) const
{
if( has_hfw )
return layout_item->heightForWidth(w);
else
return layout_item->sizeHint().y();
}
//----------------------------------------------------------------------------
int Layout::ItemData::mhfw(int w) const
{
if( has_hfw )
return layout_item->minimumHeightForWidth(w);
else
return layout_item->minimumSize().y();
}
//----------------------------------------------------------------------------
void Layout::safeAdd(int& a, int b)
{
@ -122,6 +141,15 @@ namespace canvas
d.padding = d.padding_orig;
d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size);
SG_LOG(
SG_GUI,
SG_DEBUG,
i << ") initial=" << d.size
<< ", min=" << d.min_size
<< ", hint=" << d.size_hint
<< ", max=" << d.max_size
);
if( d.done )
{
_num_not_done -= 1;

View File

@ -83,10 +83,14 @@ namespace canvas
padding, //<! padding before element (layouted)
size, //<! layouted size
stretch; //<! stretch factor
bool done; //<! layouting done
bool has_hfw : 1, //<! height for width
done : 1; //<! layouting done
/** Clear values (reset to default/empty state) */
void reset();
int hfw(int w) const;
int mhfw(int w) const;
};
/**

View File

@ -78,6 +78,24 @@ namespace canvas
return _max_size;
}
//----------------------------------------------------------------------------
bool LayoutItem::hasHeightForWidth() const
{
return false;
}
//----------------------------------------------------------------------------
int LayoutItem::heightForWidth(int w) const
{
return -1;
}
//------------------------------------------------------------------------------
int LayoutItem::minimumHeightForWidth(int w) const
{
return heightForWidth(w);
}
//----------------------------------------------------------------------------
void LayoutItem::invalidate()
{

View File

@ -63,6 +63,10 @@ namespace canvas
*/
SGVec2i maximumSize() const;
virtual bool hasHeightForWidth() const;
virtual int heightForWidth(int w) const;
virtual int minimumHeightForWidth(int w) const;
/**
* Mark all cached data as invalid and require it to be recalculated.
*/

View File

@ -65,6 +65,20 @@ namespace canvas
_set_geometry = func;
}
//----------------------------------------------------------------------------
void NasalWidget::setHeightForWidthFunc(const HeightForWidthFunc& func)
{
_height_for_width = func;
invalidateParent();
}
//----------------------------------------------------------------------------
void NasalWidget::setMinimumHeightForWidthFunc(const HeightForWidthFunc& func)
{
_min_height_for_width = func;
invalidateParent();
}
//----------------------------------------------------------------------------
void NasalWidget::setSizeHint(const SGVec2i& s)
{
@ -97,6 +111,28 @@ namespace canvas
invalidateParent();
}
//----------------------------------------------------------------------------
bool NasalWidget::hasHeightForWidth() const
{
return !_height_for_width.empty() || !_min_height_for_width.empty();
}
//----------------------------------------------------------------------------
int NasalWidget::heightForWidth(int w) const
{
return callHeightForWidthFunc( _height_for_width.empty()
? _min_height_for_width
: _height_for_width, w );
}
//----------------------------------------------------------------------------
int NasalWidget::minimumHeightForWidth(int w) const
{
return callHeightForWidthFunc( _min_height_for_width.empty()
? _height_for_width
: _min_height_for_width, w );
}
//----------------------------------------------------------------------------
static naRef f_makeNasalWidget(const nasal::CallContext& ctx)
{
@ -112,6 +148,9 @@ namespace canvas
.bases<LayoutItemRef>()
.bases<nasal::ObjectRef>()
.method("setSetGeometryFunc", &NasalWidget::setSetGeometryFunc)
.method("setMinimumHeightForWidthFunc",
&NasalWidget::setMinimumHeightForWidthFunc)
.method("setHeightForWidthFunc", &NasalWidget::setHeightForWidthFunc)
.method("setSizeHint", &NasalWidget::setSizeHint)
.method("setMinimumSize", &NasalWidget::setMinimumSize)
.method("setMaximumSize", &NasalWidget::setMaximumSize);
@ -120,6 +159,31 @@ namespace canvas
widget_hash.set("new", &f_makeNasalWidget);
}
//----------------------------------------------------------------------------
int NasalWidget::callHeightForWidthFunc( const HeightForWidthFunc& hfw,
int w ) const
{
if( hfw.empty() )
return -1;
naContext c = naNewContext();
try
{
return hfw(nasal::to_nasal(c, const_cast<NasalWidget*>(this)), w);
}
catch( std::exception const& ex )
{
SG_LOG(
SG_GUI,
SG_WARN,
"NasalWidget.heightForWidth: callback error: '" << ex.what() << "'"
);
}
naFreeContext(c);
return -1;
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::sizeHintImpl() const
{

View File

@ -40,6 +40,7 @@ namespace canvas
public:
typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
typedef boost::function<int (nasal::Me, int)> HeightForWidthFunc;
/**
*
@ -51,11 +52,17 @@ namespace canvas
virtual void setGeometry(const SGRecti& geom);
void setSetGeometryFunc(const SetGeometryFunc& func);
void setHeightForWidthFunc(const HeightForWidthFunc& func);
void setMinimumHeightForWidthFunc(const HeightForWidthFunc& func);
void setSizeHint(const SGVec2i& s);
void setMinimumSize(const SGVec2i& s);
void setMaximumSize(const SGVec2i& s);
virtual bool hasHeightForWidth() const;
virtual int heightForWidth(int w) const;
virtual int minimumHeightForWidth(int w) const;
/**
* @param ns Namespace to register the class interface
*/
@ -63,6 +70,11 @@ namespace canvas
protected:
SetGeometryFunc _set_geometry;
HeightForWidthFunc _height_for_width,
_min_height_for_width;
int callHeightForWidthFunc( const HeightForWidthFunc& hfw,
int w ) const;
virtual SGVec2i sizeHintImpl() const;
virtual SGVec2i minimumSizeImpl() const;

View File

@ -44,7 +44,7 @@ class TestWidget:
public:
TestWidget( const SGVec2i& min_size,
const SGVec2i& size_hint,
const SGVec2i& max_size )
const SGVec2i& max_size = MAX_SIZE )
{
_size_hint = size_hint;
_min_size = min_size;
@ -74,6 +74,34 @@ class TestWidget:
virtual SGVec2i maximumSizeImpl() const { return _max_size; }
};
class TestWidgetHFW:
public TestWidget
{
public:
TestWidgetHFW( const SGVec2i& min_size,
const SGVec2i& size_hint,
const SGVec2i& max_size = MAX_SIZE ):
TestWidget(min_size, size_hint, max_size)
{
}
virtual bool hasHeightForWidth() const
{
return true;
}
virtual int heightForWidth(int w) const
{
return _size_hint.x() * _size_hint.y() / w;
}
virtual int minimumHeightForWidth(int w) const
{
return _min_size.x() * _min_size.y() / w;
}
};
typedef SGSharedPtr<TestWidget> TestWidgetRef;
//------------------------------------------------------------------------------
@ -255,6 +283,7 @@ BOOST_AUTO_TEST_CASE( vertical_layout)
vbox.setDirection(sc::BoxLayout::BottomToTop);
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( boxlayout_insert_remove )
{
sc::HBoxLayout hbox;
@ -282,6 +311,89 @@ BOOST_AUTO_TEST_CASE( boxlayout_insert_remove )
BOOST_CHECK_EQUAL(hbox.itemAt(0), w1);
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( boxlayout_hfw )
{
TestWidgetRef w1( new TestWidgetHFW( SGVec2i(16, 16),
SGVec2i(32, 32) ) ),
w2( new TestWidgetHFW( SGVec2i(24, 24),
SGVec2i(48, 48) ) );
BOOST_CHECK_EQUAL(w1->heightForWidth(16), 64);
BOOST_CHECK_EQUAL(w1->minimumHeightForWidth(16), 16);
BOOST_CHECK_EQUAL(w2->heightForWidth(24), 96);
BOOST_CHECK_EQUAL(w2->minimumHeightForWidth(24), 24);
TestWidgetRef w_no_hfw( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32) ) );
BOOST_CHECK(!w_no_hfw->hasHeightForWidth());
BOOST_CHECK_EQUAL(w_no_hfw->heightForWidth(16), -1);
BOOST_CHECK_EQUAL(w_no_hfw->minimumHeightForWidth(16), -1);
// horizontal
sc::HBoxLayout hbox;
hbox.setSpacing(5);
hbox.addItem(w1);
hbox.addItem(w2);
BOOST_CHECK_EQUAL(hbox.heightForWidth(45), w2->heightForWidth(24));
BOOST_CHECK_EQUAL(hbox.heightForWidth(85), w2->heightForWidth(48));
hbox.addItem(w_no_hfw);
BOOST_CHECK_EQUAL(hbox.heightForWidth(66), 96);
BOOST_CHECK_EQUAL(hbox.heightForWidth(122), 48);
BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(66), 24);
BOOST_CHECK_EQUAL(hbox.minimumHeightForWidth(122), 16);
hbox.setGeometry(SGRecti(0, 0, 66, 24));
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 16, 24));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(21, 0, 24, 24));
BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(50, 0, 16, 24));
// vertical
sc::VBoxLayout vbox;
vbox.setSpacing(5);
vbox.addItem(w1);
vbox.addItem(w2);
BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 143);
BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 74);
BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 39);
BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 22);
vbox.addItem(w_no_hfw);
BOOST_CHECK_EQUAL(vbox.heightForWidth(24), 180);
BOOST_CHECK_EQUAL(vbox.heightForWidth(48), 111);
BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(24), 60);
BOOST_CHECK_EQUAL(vbox.minimumHeightForWidth(48), 43);
SGVec2i min_size = vbox.minimumSize(),
size_hint = vbox.sizeHint();
BOOST_CHECK_EQUAL(min_size, SGVec2i(24, 66));
BOOST_CHECK_EQUAL(size_hint, SGVec2i(48, 122));
vbox.setGeometry(SGRecti(0, 0, 24, 122));
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 24, 33));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 38, 24, 47));
BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 90, 24, 32));
// Vertical layouting modifies size hints, so check if they are correctly
// restored
BOOST_CHECK_EQUAL(min_size, vbox.minimumSize());
BOOST_CHECK_EQUAL(size_hint, vbox.sizeHint());
vbox.setGeometry(SGRecti(0, 0, 50, 122));
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 50, 44));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 49, 50, 70));
BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 124, 50, 56));
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( nasal_layout )
{

View File

@ -34,7 +34,7 @@ class SGRect
}
SGRect(const SGVec2<T>& pt):
explicit SGRect(const SGVec2<T>& pt):
_min(pt),
_max(pt)
{

View File

@ -144,6 +144,9 @@ namespace nasal
boost::function<Sig>
from_nasal_helper(naContext c, naRef ref, boost::function<Sig>*)
{
if( naIsNil(ref) )
return boost::function<Sig>();
if( !naIsCode(ref)
&& !naIsCCode(ref)
&& !naIsFunc(ref) )