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)
{
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)
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()) )
{
(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);
_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
{
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)() );
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];
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;
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;
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)
{
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;
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;
};
/**
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()
{
*/
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.
*/
_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)
{
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)
{
.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);
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
{
public:
typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
+ typedef boost::function<int (nasal::Me, int)> HeightForWidthFunc;
/**
*
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
*/
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;
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;
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;
//------------------------------------------------------------------------------
vbox.setDirection(sc::BoxLayout::BottomToTop);
}
+//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( boxlayout_insert_remove )
{
sc::HBoxLayout hbox;
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 )
{
}
- SGRect(const SGVec2<T>& pt):
+ explicit SGRect(const SGVec2<T>& pt):
_min(pt),
_max(pt)
{
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) )