Canvas: basic layouting system.

Support for vertical and horizontal layouts. More layouts and
widgets can be created using Nasal.
This commit is contained in:
Thomas Geymayer
2014-05-31 02:40:59 +02:00
parent 3c0544c9a6
commit b36cdebe8b
19 changed files with 1573 additions and 12 deletions

View File

@@ -32,5 +32,6 @@ set(SOURCES
add_subdirectory(ShivaVG/src)
add_subdirectory(elements)
add_subdirectory(events)
add_subdirectory(layout)
simgear_scene_component(canvas canvas "${SOURCES}" "${HEADERS}")

View File

@@ -191,6 +191,14 @@ namespace canvas
return _root_group;
}
//----------------------------------------------------------------------------
void Canvas::setLayout(const LayoutRef& layout)
{
_layout = layout;
_layout->setCanvas(this);
_status |= LAYOUT_DIRTY;
}
//----------------------------------------------------------------------------
void Canvas::enableRendering(bool force)
{
@@ -202,11 +210,10 @@ namespace canvas
//----------------------------------------------------------------------------
void Canvas::update(double delta_time_sec)
{
if( (!_texture.serviceable() && _status != STATUS_DIRTY)
|| (_status & CREATE_FAILED) )
if( _status & (CREATE_FAILED | MISSING_SIZE) )
return;
if( _status == STATUS_DIRTY )
if( _status & STATUS_DIRTY )
{
_texture.setSize(_size_x, _size_y);
@@ -248,6 +255,17 @@ namespace canvas
}
}
if( _layout )
{
if( (_status & LAYOUT_DIRTY) )
{
_layout->setGeometry(SGRecti(0, 0, _view_width, _view_height));
_status &= ~LAYOUT_DIRTY;
}
else
_layout->update();
}
if( _visible || _render_always )
{
BOOST_FOREACH(CanvasWeakPtr canvas_weak, _child_canvases)
@@ -392,6 +410,7 @@ namespace canvas
if( _view_width == w )
return;
_view_width = w;
_status |= LAYOUT_DIRTY;
_texture.setViewSize(_view_width, _view_height);
}
@@ -402,6 +421,7 @@ namespace canvas
if( _view_height == h )
return;
_view_height = h;
_status |= LAYOUT_DIRTY;
_texture.setViewSize(_view_width, _view_height);
}
@@ -657,7 +677,7 @@ namespace canvas
_status_msg = "Missing size-y";
else if( _status & CREATE_FAILED )
_status_msg = "Creating render target failed";
else if( _status == STATUS_DIRTY )
else if( _status & STATUS_DIRTY )
_status_msg = "Creation pending...";
else
_status_msg = "Ok";

View File

@@ -23,6 +23,7 @@
#include "ODGauge.hxx"
#include <simgear/canvas/elements/CanvasGroup.hxx>
#include <simgear/canvas/layout/Layout.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/props/propertyObject.hxx>
@@ -48,9 +49,11 @@ namespace canvas
enum StatusFlags
{
STATUS_OK,
STATUS_DIRTY = 1,
MISSING_SIZE_X = STATUS_DIRTY << 1,
STATUS_DIRTY = 1,
LAYOUT_DIRTY = STATUS_DIRTY << 1,
MISSING_SIZE_X = LAYOUT_DIRTY << 1,
MISSING_SIZE_Y = MISSING_SIZE_X << 1,
MISSING_SIZE = MISSING_SIZE_X | MISSING_SIZE_Y,
CREATE_FAILED = MISSING_SIZE_Y << 1
};
@@ -121,6 +124,12 @@ namespace canvas
*/
GroupPtr getRootGroup();
/**
* Set the layout of the canvas (the layout will automatically update with
* the viewport size of the canvas)
*/
void setLayout(const LayoutRef& layout);
/**
* Enable rendering for the next frame
*
@@ -195,7 +204,9 @@ namespace canvas
_visible;
ODGauge _texture;
GroupPtr _root_group;
GroupPtr _root_group;
LayoutRef _layout;
CullCallbackPtr _cull_callback;
bool _render_always; //<! Used to disable automatic lazy rendering (culling)

View File

@@ -125,6 +125,11 @@ namespace canvas
void Window::setCanvasContent(CanvasPtr canvas)
{
_canvas_content = canvas;
if( _layout )
{
canvas->setLayout(_layout);
_layout.clear();
}
if( _image_content )
// Placement within decoration canvas
@@ -139,6 +144,16 @@ namespace canvas
return _canvas_content;
}
//----------------------------------------------------------------------------
void Window::setLayout(const LayoutRef& layout)
{
CanvasPtr canvas = _canvas_content.lock();
if( canvas )
canvas->setLayout(layout);
else
_layout = layout; // keep layout until content canvas is set
}
//----------------------------------------------------------------------------
CanvasPtr Window::getCanvasDecoration() const
{

View File

@@ -20,6 +20,7 @@
#define CANVAS_WINDOW_HXX_
#include <simgear/canvas/elements/CanvasImage.hxx>
#include <simgear/canvas/layout/Layout.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/props/propertyObject.hxx>
@@ -34,7 +35,8 @@ namespace canvas
{
class Window:
public Image
public Image,
public LayoutItem
{
public:
static const std::string TYPE_NAME;
@@ -75,6 +77,8 @@ namespace canvas
void setCanvasContent(CanvasPtr canvas);
simgear::canvas::CanvasWeakPtr getCanvasContent() const;
void setLayout(const LayoutRef& layout);
CanvasPtr getCanvasDecoration() const;
bool isResizable() const;
@@ -101,6 +105,7 @@ namespace canvas
CanvasPtr _canvas_decoration;
CanvasWeakPtr _canvas_content;
LayoutRef _layout;
ImagePtr _image_content,
_image_shadow;

View File

@@ -167,7 +167,7 @@ namespace canvas
/**
* Get bounding box (may not be as tight as bounding box returned by
* #getTightBounds)
* #getTightBoundingBox)
*/
osg::BoundingBox getBoundingBox() const;

View File

@@ -0,0 +1,266 @@
// Align items horizontally or vertically in a box
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "BoxLayout.hxx"
#include <simgear/canvas/Canvas.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
BoxLayout::BoxLayout(Direction dir):
_padding(5)
{
setDirection(dir);
}
//----------------------------------------------------------------------------
void BoxLayout::addItem(const LayoutItemRef& item)
{
return addItem(item, 0);
}
//----------------------------------------------------------------------------
void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
{
ItemData item_data = {0};
item_data.layout_item = item;
item_data.stretch = std::max(0, stretch);
item->setCanvas(_canvas);
item->setParent(this);
_layout_items.push_back(item_data);
invalidate();
}
//----------------------------------------------------------------------------
void BoxLayout::setStretch(size_t index, int stretch)
{
if( index >= _layout_items.size() )
return;
_layout_items.at(index).stretch = std::max(0, stretch);
invalidate();
}
//----------------------------------------------------------------------------
int BoxLayout::stretch(size_t index) const
{
if( index >= _layout_items.size() )
return 0;
return _layout_items.at(index).stretch;
}
//----------------------------------------------------------------------------
void BoxLayout::setSpacing(int spacing)
{
if( spacing == _padding )
return;
_padding = spacing;
invalidate();
}
//----------------------------------------------------------------------------
int BoxLayout::spacing() const
{
return _padding;
}
//----------------------------------------------------------------------------
void BoxLayout::setDirection(Direction dir)
{
_get_layout_coord = &SGVec2i::x;
_get_fixed_coord = &SGVec2i::y;
if( dir == TopToBottom || dir == BottomToTop )
std::swap(_get_layout_coord, _get_fixed_coord);
_reverse = (dir == RightToLeft || dir == BottomToTop );
invalidate();
}
//----------------------------------------------------------------------------
BoxLayout::Direction BoxLayout::direction() const
{
if( _get_layout_coord == static_cast<CoordGetter>(&SGVec2i::x) )
return _reverse ? RightToLeft : LeftToRight;
else
return _reverse ? BottomToTop : TopToBottom;
}
//----------------------------------------------------------------------------
void BoxLayout::setCanvas(const CanvasWeakPtr& canvas)
{
_canvas = canvas;
for(size_t i = 0; i < _layout_items.size(); ++i)
_layout_items[i].layout_item->setCanvas(canvas);
}
//----------------------------------------------------------------------------
void BoxLayout::updateSizeHints() const
{
SGVec2i min_size(0, 0),
max_size(0, 0),
size_hint(0, 0);
_layout_data.reset();
bool is_first = true;
for(size_t i = 0; i < _layout_items.size(); ++i)
{
// TODO check visible
ItemData& item_data = _layout_items[i];
LayoutItem const& item = *item_data.layout_item;
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)();
if( is_first )
{
item_data.padding_orig = 0;
is_first = false;
}
else
item_data.padding_orig = _padding;
_layout_data.padding += item_data.padding_orig;
// Add sizes of all children in layout direction
safeAdd(min_size.x(), item_data.min_size);
safeAdd(max_size.x(), item_data.max_size);
safeAdd(size_hint.x(), item_data.size_hint);
// Take maximum in fixed (non-layouted) direction
min_size.y() = std::max( min_size.y(),
(item.minimumSize().*_get_fixed_coord)() );
max_size.y() = std::max( max_size.y(),
(item.maximumSize().*_get_fixed_coord)() );
size_hint.y() = std::max( min_size.y(),
(item.sizeHint().*_get_fixed_coord)() );
}
safeAdd(min_size.x(), _layout_data.padding);
safeAdd(max_size.x(), _layout_data.padding);
safeAdd(size_hint.x(), _layout_data.padding);
_layout_data.min_size = min_size.x();
_layout_data.max_size = max_size.x();
_layout_data.size_hint = size_hint.x();
_min_size.x() = (min_size.*_get_layout_coord)();
_max_size.x() = (max_size.*_get_layout_coord)();
_size_hint.x() = (size_hint.*_get_layout_coord)();
_min_size.y() = (min_size.*_get_fixed_coord)();
_max_size.y() = (max_size.*_get_fixed_coord)();
_size_hint.y() = (size_hint.*_get_fixed_coord)();
_flags &= ~SIZE_INFO_DIRTY;
}
//----------------------------------------------------------------------------
SGVec2i BoxLayout::sizeHintImpl() const
{
updateSizeHints();
return _size_hint;
}
//----------------------------------------------------------------------------
SGVec2i BoxLayout::minimumSizeImpl() const
{
updateSizeHints();
return _min_size;
}
//----------------------------------------------------------------------------
SGVec2i BoxLayout::maximumSizeImpl() const
{
updateSizeHints();
return _max_size;
}
//----------------------------------------------------------------------------
void BoxLayout::doLayout(const SGRecti& geom)
{
if( _flags & SIZE_INFO_DIRTY )
updateSizeHints();
_layout_data.size = (geom.size().*_get_layout_coord)();
distribute(_layout_items, _layout_data);
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];
cur_pos.x() += _reverse ? -data.padding - data.size : data.padding;
SGVec2i size(
data.size,
std::min( (data.layout_item->maximumSize().*_get_fixed_coord)(),
fixed_size )
);
// Center in fixed direction (TODO allow specifying alignment)
int offset_fixed = (fixed_size - size.y()) / 2;
cur_pos.y() += offset_fixed;
data.layout_item->setGeometry(SGRecti(
(cur_pos.*_get_layout_coord)(),
(cur_pos.*_get_fixed_coord)(),
(size.*_get_layout_coord)(),
(size.*_get_fixed_coord)()
));
if( !_reverse )
cur_pos.x() += data.size;
cur_pos.y() -= offset_fixed;
}
}
//----------------------------------------------------------------------------
HBoxLayout::HBoxLayout():
BoxLayout(LeftToRight)
{
}
//----------------------------------------------------------------------------
VBoxLayout::VBoxLayout():
BoxLayout(TopToBottom)
{
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,114 @@
// Align items horizontally or vertically in a box
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_CANVAS_BOX_LAYOUT_HXX_
#define SG_CANVAS_BOX_LAYOUT_HXX_
#include "Layout.hxx"
namespace simgear
{
namespace canvas
{
class BoxLayout:
public Layout
{
public:
enum Direction
{
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop
};
BoxLayout(Direction dir);
virtual void addItem(const LayoutItemRef& item);
void addItem(const LayoutItemRef& item, int stretch);
/**
* Set the stretch factor of the item at position @a index to @a stretch.
*/
void setStretch(size_t index, int stretch);
/**
* Get the stretch factor of the item at position @a index
*/
int stretch(size_t index) const;
virtual void setSpacing(int spacing);
virtual int spacing() const;
void setDirection(Direction dir);
Direction direction() const;
virtual void setCanvas(const CanvasWeakPtr& canvas);
protected:
typedef const int& (SGVec2i::*CoordGetter)() const;
CoordGetter _get_layout_coord, //<! getter for coordinate in layout
// direction
_get_fixed_coord; //<! getter for coordinate in secondary
// (fixed) direction
int _padding;
bool _reverse; //<! if true, right-to-left/bottom-to-top layouting
mutable std::vector<ItemData> _layout_items;
mutable ItemData _layout_data;
void updateSizeHints() const;
virtual SGVec2i sizeHintImpl() const;
virtual SGVec2i minimumSizeImpl() const;
virtual SGVec2i maximumSizeImpl() const;
virtual void doLayout(const SGRecti& geom);
};
/**
* Shortcut for creating a horizontal box layout
*/
class HBoxLayout:
public BoxLayout
{
public:
HBoxLayout();
};
/**
* Shortcut for creating a vertical box layout
*/
class VBoxLayout:
public BoxLayout
{
public:
VBoxLayout();
};
} // namespace canvas
} // namespace simgear
#endif /* SG_CANVAS_BOX_LAYOUT_HXX_ */

View File

@@ -0,0 +1,22 @@
include (SimGearComponent)
set(HEADERS
BoxLayout.hxx
Layout.hxx
LayoutItem.hxx
NasalWidget.hxx
)
set(SOURCES
BoxLayout.cxx
Layout.cxx
LayoutItem.cxx
NasalWidget.cxx
)
simgear_scene_component(canvas-layout canvas/layout "${SOURCES}" "${HEADERS}")
add_boost_test(canvas_layout
SOURCES canvas_layout_test.cxx
LIBRARIES ${TEST_LIBS}
)

View File

@@ -0,0 +1,243 @@
// Basic class for canvas layouts
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "Layout.hxx"
#include <simgear/debug/logstream.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
void Layout::update()
{
if( !(_flags & (LAYOUT_DIRTY | SIZE_INFO_DIRTY)) )
return;
doLayout(_geometry);
_flags &= ~LAYOUT_DIRTY;
}
//----------------------------------------------------------------------------
void Layout::invalidate()
{
LayoutItem::invalidate();
_flags |= LAYOUT_DIRTY;
}
//----------------------------------------------------------------------------
void Layout::setGeometry(const SGRecti& geom)
{
if( geom == _geometry )
return;
_geometry = geom;
_flags |= LAYOUT_DIRTY;
update();
}
//----------------------------------------------------------------------------
void Layout::ItemData::reset()
{
layout_item = 0;
size_hint = 0;
min_size = 0;
max_size = 0;
padding_orig= 0;
padding = 0;
size = 0;
stretch = 0;
done = false;
}
//----------------------------------------------------------------------------
void Layout::safeAdd(int& a, int b)
{
if( SGLimits<int>::max() - b < a )
a = SGLimits<int>::max();
else
a += b;
}
//----------------------------------------------------------------------------
void Layout::distribute(std::vector<ItemData>& items, const ItemData& space)
{
const int num_children = static_cast<int>(items.size());
_num_not_done = num_children;
SG_LOG( SG_GUI,
SG_DEBUG,
"Layout::distribute(" << num_children << " items)" );
if( space.size < space.min_size )
{
// TODO
SG_LOG( SG_GUI, SG_WARN, "Layout: not enough size (not implemented)");
}
else if( space.size < space.max_size )
{
_sum_stretch = 0;
_space_stretch = 0;
bool less_then_hint = space.size < space.size_hint;
// Give min_size/size_hint to all items
_space_left = space.size
- (less_then_hint ? space.min_size : space.size_hint);
for(int i = 0; i < num_children; ++i)
{
ItemData& d = items[i];
d.size = less_then_hint ? d.min_size : d.size_hint;
d.padding = d.padding_orig;
d.done = d.size >= (less_then_hint ? d.size_hint : d.max_size);
if( d.done )
{
_num_not_done -= 1;
continue;
}
if( d.stretch > 0 )
{
_sum_stretch += d.stretch;
_space_stretch += d.size;
}
}
// Distribute remaining space to increase the size of each item up to its
// size_hint/max_size
while( _space_left > 0 )
{
if( _num_not_done <= 0 )
{
SG_LOG(SG_GUI, SG_WARN, "space left, but no more items?");
break;
}
int space_per_element = std::max(1, _space_left / _num_not_done);
SG_LOG(SG_GUI, SG_DEBUG, "space/element=" << space_per_element);
for(int i = 0; i < num_children; ++i)
{
ItemData& d = items[i];
SG_LOG(
SG_GUI,
SG_DEBUG,
i << ") left=" << _space_left
<< ", not_done=" << _num_not_done
<< ", sum=" << _sum_stretch
<< ", stretch=" << _space_stretch
<< ", stretch/unit=" << _space_stretch / std::max(1, _sum_stretch)
);
if( d.done )
continue;
if( _sum_stretch > 0 && d.stretch <= 0 )
d.done = true;
else
{
int target_size = 0;
int max_size = less_then_hint ? d.size_hint : d.max_size;
if( _sum_stretch > 0 )
{
target_size = (d.stretch * (_space_left + _space_stretch))
/ _sum_stretch;
// Item would be smaller than minimum size or larger than maximum
// size, so just keep bounded size and ignore stretch factor
if( target_size <= d.size || target_size >= max_size )
{
d.done = true;
_sum_stretch -= d.stretch;
_space_stretch -= d.size;
if( target_size >= max_size )
target_size = max_size;
else
target_size = d.size;
}
else
_space_stretch += target_size - d.size;
}
else
{
// Give space evenly to all remaining elements in this round
target_size = d.size + std::min(_space_left, space_per_element);
if( target_size >= max_size )
{
d.done = true;
target_size = max_size;
}
}
int old_size = d.size;
d.size = target_size;
_space_left -= d.size - old_size;
}
if( d.done )
{
_num_not_done -= 1;
if( _sum_stretch <= 0 && d.stretch > 0 )
// Distribute remaining space evenly to all non-stretchable items
// in a new round
break;
}
}
}
}
else
{
_space_left = space.size - space.max_size;
for(int i = 0; i < num_children; ++i)
{
ItemData& d = items[i];
d.size = d.max_size;
// Add superfluous space as padding
d.padding = d.padding_orig + _space_left
// Padding after last child...
/ (_num_not_done + 1);
_space_left -= d.padding - d.padding_orig;
_num_not_done -= 1;
}
}
SG_LOG(SG_GUI, SG_DEBUG, "distribute:");
for(int i = 0; i < num_children; ++i)
{
ItemData const& d = items[i];
SG_LOG( SG_GUI,
SG_DEBUG,
i << ") pad=" << d.padding
<< ", size = " << d.size );
}
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,98 @@
// Basic class for canvas layouts
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_CANVAS_LAYOUT_HXX_
#define SG_CANVAS_LAYOUT_HXX_
#include "LayoutItem.hxx"
#include <vector>
namespace simgear
{
namespace canvas
{
class Layout:
public LayoutItem
{
public:
void update();
virtual void invalidate();
virtual void setGeometry(const SGRecti& geom);
virtual void addItem(const LayoutItemRef& item) = 0;
virtual void setSpacing(int spacing) = 0;
virtual int spacing() const = 0;
protected:
enum LayoutFlags
{
LAYOUT_DIRTY = LayoutItem::LAST_FLAG << 1,
LAST_FLAG = LAYOUT_DIRTY
};
struct ItemData
{
LayoutItemRef layout_item;
int size_hint,
min_size,
max_size,
padding_orig, //<! original padding as specified by the user
padding, //<! padding before element (layouted)
size, //<! layouted size
stretch; //<! stretch factor
bool done; //<! layouting done
/** Clear values (reset to default/empty state) */
void reset();
};
/**
* Override to implement the actual layouting
*/
virtual void doLayout(const SGRecti& geom) = 0;
/**
* Add two integers taking care of overflow (limit to INT_MAX)
*/
static void safeAdd(int& a, int b);
/**
* Distribute the available @a space to all @a items
*/
void distribute(std::vector<ItemData>& items, const ItemData& space);
private:
int _num_not_done, //<! number of children not layouted yet
_sum_stretch, //<! sum of stretch factors of all not yet layouted
// children
_space_stretch,//<! space currently assigned to all not yet layouted
// stretchable children
_space_left; //<! remaining space not used by any child yet
};
typedef SGSharedPtr<Layout> LayoutRef;
} // namespace canvas
} // namespace simgear
#endif /* SG_CANVAS_LAYOUT_HXX_ */

View File

@@ -0,0 +1,151 @@
// Basic element for layouting canvas elements
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "LayoutItem.hxx"
#include <simgear/canvas/Canvas.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
LayoutItem::LayoutItem():
_flags(0)
{
invalidate();
}
//----------------------------------------------------------------------------
LayoutItem::~LayoutItem()
{
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::sizeHint() const
{
if( _flags & SIZE_HINT_DIRTY )
{
_size_hint = sizeHintImpl();
_flags &= ~SIZE_HINT_DIRTY;
}
return _size_hint;
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::minimumSize() const
{
if( _flags & MINIMUM_SIZE_DIRTY )
{
_min_size = minimumSizeImpl();
_flags &= ~MINIMUM_SIZE_DIRTY;
}
return _min_size;
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::maximumSize() const
{
if( _flags & MAXIMUM_SIZE_DIRTY )
{
_max_size = maximumSizeImpl();
_flags &= ~MAXIMUM_SIZE_DIRTY;
}
return _max_size;
}
//----------------------------------------------------------------------------
void LayoutItem::invalidate()
{
_flags |= SIZE_HINT_DIRTY
| MINIMUM_SIZE_DIRTY
| MAXIMUM_SIZE_DIRTY;
invalidateParent();
}
//----------------------------------------------------------------------------
void LayoutItem::invalidateParent()
{
LayoutItemRef parent = _parent.lock();
if( parent )
parent->invalidate();
}
//----------------------------------------------------------------------------
void LayoutItem::setGeometry(const SGRecti& geom)
{
_geometry = geom;
}
//----------------------------------------------------------------------------
SGRecti LayoutItem::geometry() const
{
return _geometry;
}
//----------------------------------------------------------------------------
void LayoutItem::setCanvas(const CanvasWeakPtr& canvas)
{
_canvas = canvas;
}
//----------------------------------------------------------------------------
CanvasPtr LayoutItem::getCanvas() const
{
return _canvas.lock();
}
//----------------------------------------------------------------------------
void LayoutItem::setParent(const LayoutItemWeakRef& parent)
{
_parent = parent;
LayoutItemRef parent_ref = parent.lock();
setCanvas(parent_ref ? parent_ref->_canvas : CanvasWeakPtr());
}
//----------------------------------------------------------------------------
LayoutItemRef LayoutItem::getParent() const
{
return _parent.lock();
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::sizeHintImpl() const
{
return SGVec2i(16, 16);
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::minimumSizeImpl() const
{
return SGVec2i(0, 0);
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::maximumSizeImpl() const
{
return SGVec2i(SGLimits<int>::max(), SGLimits<int>::max());
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,139 @@
///@file Basic element for layouting canvas elements
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_CANVAS_LAYOUT_ITEM_HXX_
#define SG_CANVAS_LAYOUT_ITEM_HXX_
#include <simgear/canvas/canvas_fwd.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/misc/stdint.hxx>
#include <simgear/structure/SGWeakReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
namespace simgear
{
namespace canvas
{
class LayoutItem;
typedef SGSharedPtr<LayoutItem> LayoutItemRef;
typedef SGWeakPtr<LayoutItem> LayoutItemWeakRef;
/**
* Base class for all layouting elements. Specializations either implement a
* layouting algorithm or a widget.
*/
class LayoutItem:
public virtual SGVirtualWeakReferenced
{
public:
LayoutItem();
virtual ~LayoutItem();
/**
* Get the preferred size of this item.
*/
SGVec2i sizeHint() const;
/**
* Get the minimum amount of the space this item requires.
*/
SGVec2i minimumSize() const;
/**
* Get the maximum amount of space this item can use.
*/
SGVec2i maximumSize() const;
/**
* Mark all cached data as invalid and require it to be recalculated.
*/
virtual void invalidate();
/**
* Mark all cached data of parent item as invalid (if it is known)
*/
void invalidateParent();
/**
* Set position and size of this element. For layouts this triggers a
* recalculation of the layout.
*/
virtual void setGeometry(const SGRecti& geom);
/**
* Get position and size of this element.
*/
virtual SGRecti geometry() const;
/**
* Set the canvas this item is attached to.
*/
virtual void setCanvas(const CanvasWeakPtr& canvas);
/**
* Get the canvas this item is attached to.
*/
CanvasPtr getCanvas() const;
/**
* Set the parent layout item (usally this is a layout).
*/
void setParent(const LayoutItemWeakRef& parent);
/**
* Get the parent layout.
*/
LayoutItemRef getParent() const;
protected:
friend class Canvas;
enum Flags
{
SIZE_HINT_DIRTY = 1,
MINIMUM_SIZE_DIRTY = SIZE_HINT_DIRTY << 1,
MAXIMUM_SIZE_DIRTY = MINIMUM_SIZE_DIRTY << 1,
SIZE_INFO_DIRTY = SIZE_HINT_DIRTY
| MINIMUM_SIZE_DIRTY
| MAXIMUM_SIZE_DIRTY,
LAST_FLAG = MAXIMUM_SIZE_DIRTY
};
CanvasWeakPtr _canvas;
LayoutItemWeakRef _parent;
SGRecti _geometry;
mutable uint32_t _flags;
mutable SGVec2i _size_hint,
_min_size,
_max_size;
virtual SGVec2i sizeHintImpl() const;
virtual SGVec2i minimumSizeImpl() const;
virtual SGVec2i maximumSizeImpl() const;
};
} // namespace canvas
} // namespace simgear
#endif /* SG_CANVAS_LAYOUT_ITEM_HXX_ */

View File

@@ -0,0 +1,158 @@
// Glue for GUI layout items implemented in Nasal space
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "NasalWidget.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/nasal/cppbind/Ghost.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
NasalWidget::NasalWidget(naRef impl):
_nasal_impl(impl)
{
assert( naIsHash(_nasal_impl.get_naRef()) );
}
//----------------------------------------------------------------------------
void NasalWidget::setGeometry(const SGRect<int>& geom)
{
if( _geometry == geom )
return;
_geometry = geom;
if( _set_geometry )
_set_geometry(_nasal_impl.get_naRef(), geom);
}
//----------------------------------------------------------------------------
void NasalWidget::setSetGeometryFunc(const SetGeometryFunc& func)
{
_set_geometry = func;
}
//----------------------------------------------------------------------------
void NasalWidget::setImpl(naRef obj)
{
_nasal_impl.reset(obj);
}
//----------------------------------------------------------------------------
naRef NasalWidget::getImpl() const
{
return _nasal_impl.get_naRef();
}
//----------------------------------------------------------------------------
void NasalWidget::setSizeHint(const SGVec2i& s)
{
if( _size_hint == s )
return;
_size_hint = s;
// TODO just invalidate size_hint? Probably not a performance issue...
invalidateParent();
}
//----------------------------------------------------------------------------
void NasalWidget::setMinimumSize(const SGVec2i& s)
{
if( _min_size == s )
return;
_min_size = s;
invalidateParent();
}
//----------------------------------------------------------------------------
void NasalWidget::setMaximumSize(const SGVec2i& s)
{
if( _max_size == s )
return;
_max_size = s;
invalidateParent();
}
//----------------------------------------------------------------------------
bool NasalWidget::_set(naContext c, const std::string& key, naRef val)
{
if( !_nasal_impl.valid() )
return false;
nasal::Hash(_nasal_impl.get_naRef(), c).set(key, val);
return true;
}
//----------------------------------------------------------------------------
static naRef f_makeNasalWidget(const nasal::CallContext& ctx)
{
return ctx.to_nasal(NasalWidgetRef(
new NasalWidget( ctx.requireArg<naRef>(0) )
));
}
//----------------------------------------------------------------------------
void NasalWidget::setupGhost(nasal::Hash& ns)
{
nasal::Ghost<NasalWidgetRef>::init("canvas.Widget")
.bases<LayoutItemRef>()
._set(&NasalWidget::_set)
.member("parents", &NasalWidget::getParents)
.method("setSetGeometryFunc", &NasalWidget::setSetGeometryFunc)
.method("setSizeHint", &NasalWidget::setSizeHint)
.method("setMinimumSize", &NasalWidget::setMinimumSize)
.method("setMaximumSize", &NasalWidget::setMaximumSize);
nasal::Hash widget_hash = ns.createHash("Widget");
widget_hash.set("new", &f_makeNasalWidget);
}
//----------------------------------------------------------------------------
naRef NasalWidget::getParents(NasalWidget& w, naContext c)
{
naRef parents[] = { w._nasal_impl.get_naRef() };
return nasal::to_nasal(c, parents);
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::sizeHintImpl() const
{
return _size_hint;
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::minimumSizeImpl() const
{
return _min_size;
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::maximumSizeImpl() const
{
return _max_size;
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,85 @@
///@file Glue for GUI widgets implemented in Nasal space
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef SG_CANVAS_NASAL_WIDGET_HXX_
#define SG_CANVAS_NASAL_WIDGET_HXX_
#include "LayoutItem.hxx"
#include <simgear/nasal/cppbind/from_nasal.hxx>
#include <simgear/nasal/cppbind/NasalHash.hxx>
namespace simgear
{
namespace canvas
{
/**
* Baseclass/ghost to create widgets with Nasal.
*/
class NasalWidget:
public LayoutItem
{
public:
typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
/**
*
* @param impl Initial implementation hash (nasal part of
* implementation)
*/
NasalWidget(naRef impl);
virtual void setGeometry(const SGRecti& geom);
void setSetGeometryFunc(const SetGeometryFunc& func);
void setImpl(naRef obj);
naRef getImpl() const;
void setSizeHint(const SGVec2i& s);
void setMinimumSize(const SGVec2i& s);
void setMaximumSize(const SGVec2i& s);
bool _set(naContext c, const std::string& key, naRef val);
/**
* @param ns Namespace to register the class interface
*/
static void setupGhost(nasal::Hash& ns);
protected:
SetGeometryFunc _set_geometry;
nasal::ObjectHolder<> _nasal_impl;
static naRef getParents(NasalWidget&, naContext);
virtual SGVec2i sizeHintImpl() const;
virtual SGVec2i minimumSizeImpl() const;
virtual SGVec2i maximumSizeImpl() const;
};
typedef SGSharedPtr<NasalWidget> NasalWidgetRef;
} // namespace canvas
} // namespace simgear
#endif /* SG_CANVAS_NASAL_WIDGET_HXX_ */

View File

@@ -0,0 +1,231 @@
// Testing canvas layouting system
//
// Copyright (C) 2014 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#define BOOST_TEST_MODULE canvas_layout
#include <BoostTestTargetConfig.h>
#include "BoxLayout.hxx"
#include "NasalWidget.hxx"
#include <simgear/debug/logstream.hxx>
#include <cstdlib>
//------------------------------------------------------------------------------
struct SetLogLevelFixture
{
SetLogLevelFixture()
{
sglog().set_log_priority(SG_DEBUG);
}
};
BOOST_GLOBAL_FIXTURE(SetLogLevelFixture);
//------------------------------------------------------------------------------
namespace sc = simgear::canvas;
class TestWidget:
public sc::LayoutItem
{
public:
TestWidget( const SGVec2i& min_size,
const SGVec2i& size_hint,
const SGVec2i& max_size )
{
_size_hint = size_hint;
_min_size = min_size;
_max_size = max_size;
}
TestWidget(const TestWidget& rhs)
{
_size_hint = rhs._size_hint;
_min_size = rhs._min_size;
_max_size = rhs._max_size;
}
void setMinSize(const SGVec2i& size) { _min_size = size; }
void setMaxSize(const SGVec2i& size) { _max_size = size; }
void setSizeHint(const SGVec2i& size) { _size_hint = size; }
virtual void setGeometry(const SGRecti& geom) { _geom = geom; }
virtual SGRecti geometry() const { return _geom; }
protected:
SGRecti _geom;
virtual SGVec2i sizeHintImpl() const { return _size_hint; }
virtual SGVec2i minimumSizeImpl() const { return _min_size; }
virtual SGVec2i maximumSizeImpl() const { return _max_size; }
};
typedef SGSharedPtr<TestWidget> TestWidgetRef;
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( horizontal_layout )
{
sc::BoxLayout box_layout(sc::BoxLayout::BottomToTop);
box_layout.setSpacing(5);
BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::BottomToTop);
BOOST_CHECK_EQUAL(box_layout.spacing(), 5);
box_layout.setDirection(sc::BoxLayout::LeftToRight);
box_layout.setSpacing(9);
BOOST_CHECK_EQUAL(box_layout.direction(), sc::BoxLayout::LeftToRight);
BOOST_CHECK_EQUAL(box_layout.spacing(), 9);
TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
SGVec2i(16, 16),
SGVec2i(16, 16) ) );
box_layout.addItem(fixed_size_widget);
BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(16, 16));
BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(16, 16));
BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(16, 16));
TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(256, 64) ) );
box_layout.addItem(limited_resize_widget);
// Combined sizes of both widget plus the padding between them
BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(41, 16));
BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(57, 32));
BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(281, 64));
// Test with different spacing/padding
box_layout.setSpacing(5);
BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(37, 16));
BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(53, 32));
BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(277, 64));
box_layout.setGeometry(SGRecti(0, 0, 128, 32));
// Fixed size for first widget and remaining space goes to second widget
BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 107, 32));
TestWidgetRef stretch_widget( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(128, 32) ) );
box_layout.addItem(stretch_widget, 1);
box_layout.update();
BOOST_CHECK_EQUAL(box_layout.minimumSize(), SGVec2i(58, 16));
BOOST_CHECK_EQUAL(box_layout.sizeHint(), SGVec2i(90, 32));
BOOST_CHECK_EQUAL(box_layout.maximumSize(), SGVec2i(410, 64));
// Due to the stretch factor only the last widget gets additional space. All
// other widgets get the preferred size.
BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(0, 8, 16, 16));
BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(21, 0, 32, 32));
BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(58, 0, 70, 32));
// Test stretch factor
TestWidgetRef fast_stretch( new TestWidget(*stretch_widget) );
sc::BoxLayout box_layout_stretch(sc::BoxLayout::LeftToRight);
box_layout_stretch.addItem(stretch_widget, 1);
box_layout_stretch.addItem(fast_stretch, 2);
box_layout_stretch.setGeometry(SGRecti(0,0,128,32));
BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 41, 32));
BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(46, 0, 82, 32));
box_layout_stretch.setGeometry(SGRecti(0,0,256,32));
BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(0, 0, 123, 32));
BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(128, 0, 128, 32));
// Test superflous space to padding
box_layout_stretch.setGeometry(SGRecti(0,0,512,32));
BOOST_CHECK_EQUAL(stretch_widget->geometry(), SGRecti(83, 0, 128, 32));
BOOST_CHECK_EQUAL(fast_stretch->geometry(), SGRecti(300, 0, 128, 32));
// Test more space then preferred, but less than maximum
{
sc::HBoxLayout hbox;
TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(9999, 32) ) ),
w2( new TestWidget(*w1) );
hbox.addItem(w1);
hbox.addItem(w2);
hbox.setGeometry( SGRecti(0, 0, 256, 32) );
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 126, 32));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(131, 0, 125, 32));
hbox.setStretch(0, 1);
hbox.setStretch(1, 1);
BOOST_CHECK_EQUAL(hbox.stretch(0), 1);
BOOST_CHECK_EQUAL(hbox.stretch(1), 1);
hbox.update();
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 125, 32));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(130, 0, 126, 32));
}
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( vertical_layout)
{
sc::BoxLayout vbox(sc::BoxLayout::TopToBottom);
vbox.setSpacing(7);
TestWidgetRef fixed_size_widget( new TestWidget( SGVec2i(16, 16),
SGVec2i(16, 16),
SGVec2i(16, 16) ) );
TestWidgetRef limited_resize_widget( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(256, 64) ) );
vbox.addItem(fixed_size_widget);
vbox.addItem(limited_resize_widget);
BOOST_CHECK_EQUAL(vbox.minimumSize(), SGVec2i(16, 39));
BOOST_CHECK_EQUAL(vbox.sizeHint(), SGVec2i(32, 55));
BOOST_CHECK_EQUAL(vbox.maximumSize(), SGVec2i(256, 87));
vbox.setGeometry(SGRecti(10, 20, 16, 55));
BOOST_CHECK_EQUAL(fixed_size_widget->geometry(), SGRecti(10, 20, 16, 16));
BOOST_CHECK_EQUAL(limited_resize_widget->geometry(), SGRecti(10, 43, 16, 32));
vbox.setDirection(sc::BoxLayout::BottomToTop);
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( nasal_layout )
{
naContext c = naNewContext();
naRef me = naNewHash(c);
sc::LayoutItemRef nasal_item( new sc::NasalWidget(me) );
naFreeContext(c);
}

View File

@@ -31,7 +31,8 @@ typedef enum {
SG_ENVIRONMENT = 0x00100000,
SG_SOUND = 0x00200000,
SG_NAVAID = 0x00400000,
SG_UNDEFD = 0x00800000, // For range checking
SG_GUI = 0x00800000,
SG_UNDEFD = 0x01000000, // For range checking
SG_ALL = 0xFFFFFFFF
} sgDebugClass;

View File

@@ -70,6 +70,7 @@ const char* debugClassToString(sgDebugClass c)
case SG_ENVIRONMENT:return "environment";
case SG_SOUND: return "sound";
case SG_NAVAID: return "navaid";
case SG_GUI: return "gui";
default: return "unknown";
}
}
@@ -438,4 +439,4 @@ void requestConsole()
global_privateLogstream->requestConsole();
}
} // of namespace simgear
} // of namespace simgear

View File

@@ -30,7 +30,7 @@ namespace simgear
*/
class PropertyBasedElement:
public SGPropertyChangeListener,
public SGWeakReferenced
public virtual SGVirtualWeakReferenced
{
public:
PropertyBasedElement(SGPropertyNode* node);