1 ///@file 2 /// A group of 2D Canvas elements 3 // 4 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com> 5 // 6 // This library is free software; you can redistribute it and/or 7 // modify it under the terms of the GNU Library General Public 8 // License as published by the Free Software Foundation; either 9 // version 2 of the License, or (at your option) any later version. 10 // 11 // This library is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 // Library General Public License for more details. 15 // 16 // You should have received a copy of the GNU Library General Public 17 // License along with this library; if not, write to the Free Software 18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 20 #include <simgear_config.h> 21 22 #include "CanvasGroup.hxx" 23 #include "CanvasImage.hxx" 24 #include "CanvasMap.hxx" 25 #include "CanvasPath.hxx" 26 #include "CanvasText.hxx" 27 28 #include <simgear/canvas/CanvasEventVisitor.hxx> 29 #include <simgear/canvas/events/MouseEvent.hxx> 30 31 namespace simgear 32 { 33 namespace canvas 34 { 35 /** 36 * Add canvas Element type to factory map 37 */ 38 template<typename ElementType> add(ElementFactories & factories)39 void add(ElementFactories& factories) 40 { 41 ElementType::staticInit(); 42 factories[ElementType::TYPE_NAME] = &Element::create<ElementType>; 43 } 44 45 //---------------------------------------------------------------------------- 46 ElementFactories Group::_child_factories; 47 const std::string Group::TYPE_NAME = "group"; 48 warnSceneGroupExpired(const char * member_name)49 void warnSceneGroupExpired(const char* member_name) 50 { 51 SG_LOG( SG_GENERAL, 52 SG_WARN, 53 "canvas::Group::" << member_name << ": Group has expired." ); 54 } 55 56 //---------------------------------------------------------------------------- staticInit()57 void Group::staticInit() 58 { 59 if( isInit<Group>() ) 60 return; 61 62 add<Group>(_child_factories); 63 add<Image>(_child_factories); 64 add<Map >(_child_factories); 65 add<Path >(_child_factories); 66 add<Text >(_child_factories); 67 } 68 69 //---------------------------------------------------------------------------- Group(const CanvasWeakPtr & canvas,const SGPropertyNode_ptr & node,const Style & parent_style,ElementWeakPtr parent)70 Group::Group( const CanvasWeakPtr& canvas, 71 const SGPropertyNode_ptr& node, 72 const Style& parent_style, 73 ElementWeakPtr parent ): 74 Element(canvas, node, parent_style, parent) 75 { 76 staticInit(); 77 } 78 79 //---------------------------------------------------------------------------- ~Group()80 Group::~Group() 81 { 82 83 } 84 85 //---------------------------------------------------------------------------- createChild(const std::string & type,const std::string & id)86 ElementPtr Group::createChild( const std::string& type, 87 const std::string& id ) 88 { 89 SGPropertyNode* node = _node->addChild(type, 0, false); 90 if( !id.empty() ) 91 node->setStringValue("id", id); 92 93 return getChild(node); 94 } 95 96 //---------------------------------------------------------------------------- getChild(const SGPropertyNode * node)97 ElementPtr Group::getChild(const SGPropertyNode* node) 98 { 99 return findChild(node, ""); 100 } 101 102 //---------------------------------------------------------------------------- getChild(const std::string & id)103 ElementPtr Group::getChild(const std::string& id) 104 { 105 return findChild(0, id); 106 } 107 108 //---------------------------------------------------------------------------- getOrCreateChild(const std::string & type,const std::string & id)109 ElementPtr Group::getOrCreateChild( const std::string& type, 110 const std::string& id ) 111 { 112 ElementPtr child = getChild(id); 113 if( child ) 114 { 115 if( child->getProps()->getNameString() == type ) 116 return child; 117 118 SG_LOG 119 ( 120 SG_GENERAL, 121 SG_WARN, 122 "Group::getOrCreateChild: type missmatch! " 123 "('" << type << "' != '" << child->getProps()->getName() << "', " 124 "id = '" << id << "')" 125 ); 126 127 return ElementPtr(); 128 } 129 130 return createChild(type, id); 131 } 132 133 //---------------------------------------------------------------------------- getElementById(const std::string & id)134 ElementPtr Group::getElementById(const std::string& id) 135 { 136 if( !_scene_group.valid() ) 137 { 138 warnSceneGroupExpired("getElementById"); 139 return {}; 140 } 141 142 // TODO check search algorithm. Not completely breadth-first and might be 143 // possible with using less dynamic memory 144 std::vector<GroupPtr> child_groups; 145 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 146 { 147 const ElementPtr& el = getChildByIndex(i); 148 if( el->get<std::string>("id") == id ) 149 return el; 150 151 if( Group* child_group = dynamic_cast<Group*>(el.get()) ) 152 child_groups.push_back(child_group); 153 } 154 155 for(auto group: child_groups) 156 { 157 if( ElementPtr el = group->getElementById(id) ) 158 return el; 159 } 160 161 return {}; 162 } 163 164 //---------------------------------------------------------------------------- clearEventListener()165 void Group::clearEventListener() 166 { 167 Element::clearEventListener(); 168 169 if( !_scene_group.valid() ) 170 return warnSceneGroupExpired("clearEventListener"); 171 172 // TODO should this be recursive? 173 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 174 getChildByIndex(i)->clearEventListener(); 175 } 176 177 //---------------------------------------------------------------------------- traverse(EventVisitor & visitor)178 bool Group::traverse(EventVisitor& visitor) 179 { 180 if( _scene_group.valid() ) 181 { 182 // Iterate in reverse order as last child is displayed on top 183 for(size_t i = _scene_group->getNumChildren(); i --> 0;) 184 { 185 if( getChildByIndex(i)->accept(visitor) ) 186 return true; 187 } 188 } 189 return false; 190 } 191 192 //---------------------------------------------------------------------------- setStyle(const SGPropertyNode * style,const StyleInfo * style_info)193 bool Group::setStyle( const SGPropertyNode* style, 194 const StyleInfo* style_info ) 195 { 196 if( !canApplyStyle(style) ) 197 return false; 198 199 bool handled = setStyleImpl(style, style_info); 200 if( style_info->inheritable ) 201 { 202 if( !_scene_group.valid() ) 203 { 204 warnSceneGroupExpired("setStyle"); 205 return false; 206 } 207 208 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 209 handled |= getChildByIndex(i)->setStyle(style, style_info); 210 } 211 212 return handled; 213 } 214 215 //---------------------------------------------------------------------------- getTransformedBounds(const osg::Matrix & m) const216 osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const 217 { 218 if( !_scene_group.valid() ) 219 { 220 warnSceneGroupExpired("getTransformedBounds"); 221 return {}; 222 } 223 224 osg::BoundingBox bb; 225 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 226 { 227 auto child = getChildByIndex(i); 228 if( !child || !child->isVisible() ) 229 continue; 230 231 bb.expandBy( child->getTransformedBounds(child->getMatrix() * m) ); 232 } 233 234 return bb; 235 } 236 237 //---------------------------------------------------------------------------- getChildFactory(const std::string & type) const238 ElementFactory Group::getChildFactory(const std::string& type) const 239 { 240 ElementFactories::iterator child_factory = _child_factories.find(type); 241 if( child_factory != _child_factories.end() ) 242 return child_factory->second; 243 244 return ElementFactory(); 245 } 246 247 //---------------------------------------------------------------------------- updateImpl(double dt)248 void Group::updateImpl(double dt) 249 { 250 Element::updateImpl(dt); 251 252 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 253 getChildByIndex(i)->update(dt); 254 } 255 256 //---------------------------------------------------------------------------- childAdded(SGPropertyNode * child)257 void Group::childAdded(SGPropertyNode* child) 258 { 259 if( child->getParent() != _node ) 260 return; 261 262 ElementFactory child_factory = getChildFactory( child->getNameString() ); 263 if( child_factory ) 264 { 265 if( !_scene_group.valid() ) 266 return warnSceneGroupExpired("childAdded"); 267 268 ElementPtr element = child_factory(_canvas, child, _style, this); 269 270 // Add to osg scene graph... 271 _scene_group->addChild(element->getSceneGroup()); 272 273 // ...and ensure correct ordering 274 handleZIndexChanged(element); 275 276 return; 277 } 278 279 StyleInfo const* style = getStyleInfo(child->getNameString()); 280 if( style && style->inheritable ) 281 _style[ child->getNameString() ] = child; 282 } 283 284 //---------------------------------------------------------------------------- childRemoved(SGPropertyNode * node)285 void Group::childRemoved(SGPropertyNode* node) 286 { 287 if( node->getParent() != _node ) 288 return; 289 290 if( getChildFactory(node->getNameString()) ) 291 { 292 if( !_scene_group.valid() ) 293 // If transform is destroyed also all children are destroyed, so we can 294 // not do anything here. 295 return; 296 297 ElementPtr child = getChild(node); 298 if( !child ) 299 SG_LOG 300 ( 301 SG_GL, 302 SG_WARN, 303 "can't removed unknown child " << node->getDisplayName() 304 ); 305 else 306 { 307 // Remove child from the scenegraph (this automatically invalidates the 308 // reference on the element hold by the MatrixTransform) 309 child->onDestroy(); 310 } 311 } 312 else 313 { 314 _style.erase( node->getNameString() ); 315 } 316 } 317 318 //---------------------------------------------------------------------------- childChanged(SGPropertyNode * node)319 void Group::childChanged(SGPropertyNode* node) 320 { 321 SGPropertyNode* parent = node->getParent(); 322 SGPropertyNode* grand_parent = parent ? parent->getParent() : nullptr; 323 324 if( grand_parent == _node 325 && node->getNameString() == "z-index" ) 326 return handleZIndexChanged(getChild(parent), node->getIntValue()); 327 } 328 329 //---------------------------------------------------------------------------- handleZIndexChanged(ElementPtr child,int z_index)330 void Group::handleZIndexChanged(ElementPtr child, int z_index) 331 { 332 if( !child || !_scene_group.valid() ) 333 return; 334 335 // Keep reference to prevent deleting while removing and re-inserting later 336 osg::ref_ptr<osg::MatrixTransform> tf = child->getSceneGroup(); 337 338 size_t index = _scene_group->getChildIndex(tf), 339 index_new = index; 340 341 for(;; ++index_new) 342 { 343 if( index_new + 1 == _scene_group->getNumChildren() ) 344 break; 345 346 // Move to end of block with same index (= move upwards until the next 347 // element has a higher index) 348 if( getChildByIndex(index_new + 1)->get<int>("z-index", 0) > z_index ) 349 break; 350 } 351 352 if( index_new == index ) 353 { 354 // We were not able to move upwards so now try downwards 355 for(;; --index_new) 356 { 357 if( index_new == 0 ) 358 break; 359 360 // Move to end of block with same index (= move downwards until the 361 // previous element has the same or a lower index) 362 if( getChildByIndex(index_new - 1)->get<int>("z-index", 0) <= z_index ) 363 break; 364 } 365 366 if( index == index_new ) 367 return; 368 } 369 370 _scene_group->removeChild(index); 371 _scene_group->insertChild(index_new, tf); 372 373 SG_LOG 374 ( 375 SG_GENERAL, 376 SG_DEBUG, 377 "canvas::Group: Moved element " << index << " to position " << index_new 378 ); 379 } 380 381 //---------------------------------------------------------------------------- getChildByIndex(size_t index) const382 ElementPtr Group::getChildByIndex(size_t index) const 383 { 384 assert( _scene_group.valid() ); 385 386 auto child = _scene_group->getChild(index); 387 if( !child ) 388 return {}; 389 390 auto ud = static_cast<OSGUserData*>(child->getUserData()); 391 return ud ? ud->element : ElementPtr(); 392 } 393 394 //---------------------------------------------------------------------------- findChild(const SGPropertyNode * node,const std::string & id) const395 ElementPtr Group::findChild( const SGPropertyNode* node, 396 const std::string& id ) const 397 { 398 if( !_scene_group.valid() ) 399 { 400 warnSceneGroupExpired("findChild"); 401 return {}; 402 } 403 404 for(size_t i = 0; i < _scene_group->getNumChildren(); ++i) 405 { 406 ElementPtr el = getChildByIndex(i); 407 408 if( node ) 409 { 410 if( el->getProps() == node ) 411 return el; 412 } 413 else 414 { 415 if( el->get<std::string>("id") == id ) 416 return el; 417 } 418 } 419 420 return {}; 421 } 422 423 } // namespace canvas 424 } // namespace simgear 425