Compare commits

..

591 Commits

Author SHA1 Message Date
Torsten Dreyer
4bd34991e9 New Version: 3.2.0 2014-07-16 14:56:49 +02:00
Thomas Geymayer
9709cfe20d canvas::BoxLayout: set stretch factor by item. 2014-07-16 19:24:41 +02:00
Thomas Geymayer
a5e99ea996 Nasal: require 0o as prefix for octal numbers.
Using just 0 as prefix for octal number can lead to confusion
where numbers could be interpreted the wrong way (oct instead of
dec).
Lets follow the same convention as Python 3 and Ecma 262 level 6
and use '0o' as prefix which can not be confused inadvertently.
2014-07-14 18:51:11 +02:00
Thomas Geymayer
d9df10fe10 canvas::Layout: keep user provided size hints.
Don't let size hints layouted or calculated by Nasal widgets
override user provided hints.
2014-07-12 17:30:33 +02:00
Thomas Geymayer
f1677f5546 SGTrackToAnimation: supress wrong warnings. 2014-07-12 11:55:38 +02:00
Thomas Geymayer
cb796e374a SGPropertyNode: add InterpolationMgr getter. 2014-07-12 01:07:28 +02:00
Thomas Geymayer
7c3491c679 Don't restore old canvasses on reset.
No need for this anymore as PRESERVE now works as expected.
Otherwise old canvasses are restored and do not work anymore
as the placements of the new canvasses do not apply.
2014-07-11 19:07:53 +02:00
Thomas Geymayer
6200b160ec canvas::Element: fix cleanup (and hopefully crash) 2014-07-01 12:09:55 +02:00
Thomas Geymayer
9868fb03a2 pkg::Package: callback for installation start.
Allow eg. Aircraft Center to add listeners to packages to
be called if installation starts.
2014-06-30 18:36:15 +02:00
Thomas Geymayer
a69130ff10 pkg: Remove Install from list after uninstall. 2014-06-30 18:34:41 +02:00
Thomas Geymayer
0b60643053 HTTPRequest/pkg::Install: do not replace callbacks.
Keep a list of callbacks to allow registering multiple callbacks
to the same event. This is consistent with eg. jQuery.Deferred
and is needed for example to open multiple dialogs showing the
progress of installing a package at the same time.
2014-06-30 18:22:24 +02:00
Thomas Geymayer
dc4644bf3a canvas::Widget: pass ghost to onRemove handler 2014-06-29 11:57:12 +02:00
Thomas Geymayer
38b766f845 canvas::BoxLayout: fix hfw layouting (fix updating size hint cache). 2014-06-28 13:08:06 +02:00
Thomas Geymayer
10d0be013e fix g++ 4.4: "warning: suggest parentheses around ‘&&’ within ‘||’"
Warning does not make sense, but the old g++ complains...
2014-06-27 01:48:08 +02:00
Thomas Geymayer
5126ae5891 SGPath: fix creating paths with permission checker.
Check permission of the whole path and not to individual
directories, as none of the intermediate directories need
to be writeable by IORules.
2014-06-26 16:07:33 +02:00
Thomas Geymayer
49bcf49db1 Nasal: fix parsing octal/hex numbers in strings as well as during lexing.
Parse the same number formats (octal, dec, hex) in literals and tokens. Was
previously quite a mess, and is still not the best solution, as lexing and
string parsing uses different implementations, although they are meant to
do exactly the same conversions.
2014-06-26 14:21:18 +02:00
Thomas Geymayer
3bbb272ad5 nasal::Ghost: improve intrusive pointer storage and weak references.
- Just increment/decrement reference count for intrusive
   smart pointers. No need to create an additional object
   on the heap.
 - Keep strong reference for weak pointer based ghosts
   to prevent destroying objects while beeing used.
2014-06-23 13:14:07 +02:00
Thomas Geymayer
645cae184b SGConditionRef typedef. No functional changes. 2014-06-22 15:35:31 +02:00
Thomas Geymayer
bd9fa7017f ShivaVG: fix call to shCopyPixels.
It is not used anyhow, but just in case...
2014-06-21 13:09:29 +02:00
Thomas Geymayer
fc9f3abfbb ShivaVG: check for zero before dividing. 2014-06-21 12:26:58 +02:00
Thomas Geymayer
f2f1b36df9 Nasal: better error message for non-numeric string. 2014-06-21 00:21:36 +02:00
Thomas Geymayer
03d2a166b7 canvas::CustomEvent: init bubbles member. 2014-06-19 13:04:21 +02:00
Thomas Geymayer
f0a76aa918 Use Boost.Test for strutils_test and add some md5/hex tests. 2014-06-18 18:22:31 +02:00
Thomas Geymayer
c144c3562c Keep md5 inside simgear and use single encodeHex implementation. 2014-06-18 18:07:42 +02:00
Thomas Geymayer
e06f9462ab Expose md5.h to flightgear for. Will need a better interface... 2014-06-17 22:32:40 +02:00
Thomas Geymayer
9aa5c3b2ae Nasal: support for standard bitwise operators.
This makes bitwise operations a lot easier^^
2014-06-17 16:59:04 +02:00
Thomas Geymayer
2c41c25fcd canvas::Text: get maximum width (if displayed on a single line). 2014-06-17 16:12:29 +02:00
Thomas Geymayer
9ffc0ae8bc canvas::NasalWidget: ensure nasal part is destroyed 2014-06-16 23:45:52 +02:00
James Turner
bb5e07d958 Package: support for variants
- Catalog keeps a map from variant IDs to packages
- Package caches its ID
- Refreshing a catalog updates existing Package instances

Dropping a package from a Catalog will now warn, need to decide
a real policy for this scenario.
2014-06-15 19:55:50 +02:00
Thomas Geymayer
5024b62c0a cppbind: queue Ghosts for deletion and delete outside gc. 2014-06-15 16:34:47 +02:00
Thomas Geymayer
82d5c605e5 canvas::Element: check if element is valid. 2014-06-15 16:30:57 +02:00
Thomas Geymayer
737b1948bf canvas::Layout: proper cleanup/update on removing items. 2014-06-15 16:30:25 +02:00
Thomas Geymayer
1225e53162 pkg: allow 'installed' as filter criterion. 2014-06-15 13:58:12 +02:00
Thomas Geymayer
b58bf443ff canvas::Layout: add clear method to remove all items. 2014-06-14 13:19:00 +02:00
Thomas Geymayer
9bef80fbef Simple Markdown parser.
Parse just a tiny subset of Markdown, to basically collapse
multiple whitespace and do paragraphs only after two newlines.
Also one-level, unordered lists are supported. The output can
be eg. directly forwarded to canvas::Text.
2014-06-13 19:12:56 +02:00
Thomas Geymayer
36cb7a752b canvas::Text: add heightForWidth method. 2014-06-13 00:08:57 +02:00
Thomas Geymayer
d3b211e787 canvas::Layout: support height-for-width layouting. 2014-06-13 00:03:40 +02:00
Thomas Geymayer
e3f0fad272 Package: fix property type (description) 2014-06-12 23:26:01 +02:00
Thomas Geymayer
e8f10dd2e8 pkg: Fix recursion in Install destructor.
Prevent creating a shared pointer from this in
destructor, which would lead to recursively
calling the descructor.
2014-06-12 23:21:56 +02:00
James Turner
edbfbd769e Package: expose file size property. 2014-06-12 18:43:27 +01:00
James Turner
dc60cf0e67 Pkg: record live installs in Catalog.
Ensures active Installs can be found immediately after creation.
2014-06-12 18:43:27 +01:00
James Turner
083b364afd Pkg: qualified ID accessor. 2014-06-12 08:50:09 +01:00
Thomas Geymayer
dd2bf418b9 canvas::Layout: remove/get child items. 2014-06-12 00:35:17 +02:00
Thomas Geymayer
97cc250047 Package: fix detection of unknown filter term. 2014-06-11 18:18:18 +02:00
Thomas Geymayer
49fc75926e Use Boost.Test to test utf8tolatin1 - others to be converted too... 2014-06-11 00:29:17 +02:00
Thomas Geymayer
9880ba39a7 pkg: Support for install callbacks and fix memory corruption. 2014-06-11 00:19:11 +02:00
James Turner
a8c8148068 Package::existingInstall helper. 2014-06-10 21:21:30 +01:00
Rebecca Palmer
adebbe006b utf8ToLatin1: add test 2014-06-10 20:45:05 +01:00
Rebecca Palmer
c3bc73ab2f utf8ToLatin1: return original instead of crashing on non-UTF-8 input
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=750859
(In the long run we should probably fix the underlying
inconsistent-text-encodings problem, but probably not in time for 3.2)
2014-06-10 19:30:09 +01:00
Thomas Geymayer
c716cfbb07 cppbind: expose SGRect as [x, y, w, h] 2014-06-10 18:42:12 +02:00
Thomas Geymayer
ade4627f1a Package: use correct tag for download url. 2014-06-10 00:45:03 +02:00
Thomas Geymayer
e508ff724c canvas::BoxLayout: add custom additional whitespace (spacer/stretch) 2014-06-08 23:30:11 +02:00
Thomas Geymayer
c54e3f8101 canvas::Image: allow aspect ratio preserving display. 2014-06-08 02:16:07 +02:00
Rebecca Palmer
6925c2a2be Downgrade "Please decompress this texture for increased portability"
to a warning (logged but not displayed by default),
to avoid needlessly annoying end users
2014-06-07 08:26:30 +01:00
Rebecca Palmer
398bf740d7 Fix over-read error in the test suite
https://buildd.debian.org/status/fetch.php?pkg=simgear&arch=mips&ver=3.0.0~git20140123%2Bf16c99-1&stamp=1391666349
2014-06-07 08:25:13 +01:00
Thomas Geymayer
e302ad092e canvas::Image: abort http requests if image is destroyed. 2014-06-05 17:25:12 +02:00
Thomas Geymayer
7a65d42a4c Canvas: fix transformation of clip rect. 2014-06-05 16:07:09 +02:00
Thomas Geymayer
d3a14bfd61 canvas: BoxLayout shared pointer typedef. 2014-06-04 17:48:46 +02:00
Thomas Geymayer
03cafe4547 Catalog: add method to get all packages in a catalog. 2014-06-04 17:47:29 +02:00
Thomas Geymayer
e5acc3f048 cppbind::Ghost: improve compiler error message for wrong usage. 2014-06-04 17:46:35 +02:00
Thomas Geymayer
4931ce0364 This has to work on windows now^^ 2014-06-04 13:41:48 +02:00
Thomas Geymayer
e3d53e854a Hopefully fixing windows build. 2014-06-04 12:17:00 +02:00
Thomas Geymayer
bdf6b25338 Now the real fix for old compilers... 2014-06-04 03:16:02 +02:00
Thomas Geymayer
5dddba26ac Fix headless build and reduce include dependencies. 2014-06-04 02:46:57 +02:00
Thomas Geymayer
746fa419ed Trying to fix problems retrieving static member address with old compilers. 2014-06-04 02:45:52 +02:00
Thomas Geymayer
7080f919e9 Fix for old versions of Boost.
No support for BOOST_STATIC_ASSERT_MSG before Boost 1.46.
2014-06-03 20:34:21 +02:00
Thomas Geymayer
c4e0014d74 cppbind::Ghost: safely use strong and weak references.
Allow using strong and weak references within the same
class hierarchy and seemlessly convert between them -
from and to Nasal.
2014-06-03 18:40:01 +02:00
Thomas Geymayer
7d9797e091 SGSharedPtr: add constructor to convert from SGWeakPtr. 2014-06-03 01:58:57 +02:00
James Turner
67a8d8049f Package::match case-insensitive string comparison 2014-06-01 11:35:26 +01:00
James Turner
c3670b211f Substring search of package name/description.
Note this only searches the current locale, should potentially also
search the default locale too.
2014-06-01 11:28:10 +01:00
Thomas Geymayer
32274027ef Missing return statement 2014-06-01 12:14:31 +02:00
Thomas Geymayer
d09943cbb8 Fix wrong include guard (identified by clang) 2014-06-01 12:12:28 +02:00
Thomas Geymayer
150039f9ba Canvas/Layout: tweak the way elements are exposed to Nasal. 2014-06-01 12:12:28 +02:00
Thomas Geymayer
820a315cbe Canvas: fix possible crash with destroyed canvasses 2014-06-01 12:12:28 +02:00
Thomas Geymayer
8abe4622b9 Canvas: trigger immediate update from property tree/Nasal. 2014-06-01 12:12:28 +02:00
James Turner
da6ab1eabc Add installedPackages() to Catalog. 2014-05-31 18:00:38 +01:00
James Turner
e3ddcbe2dd Update package classes ownership model.
Use SGReferences as the base for package classes, so they can be
exposed directly to Nasal by cppbind.
2014-05-31 16:47:12 +01:00
Thomas Geymayer
b36cdebe8b Canvas: basic layouting system.
Support for vertical and horizontal layouts. More layouts and
widgets can be created using Nasal.
2014-05-31 02:40:59 +02:00
Thomas Geymayer
3c0544c9a6 Sky dome: remove unused variables/code. 2014-05-31 00:09:56 +02:00
Thomas Geymayer
be358f8d24 cppbind: use Hash iterators to extract simgear::Map 2014-05-30 23:59:36 +02:00
Thomas Geymayer
e1abab393b Fix for clang/template dependent name lookup.
Thanks to James & Nicholas...
2014-05-30 23:57:55 +02:00
Thomas Geymayer
0b21181f1a Canvas: allow dispatching events to Canavs itself 2014-05-29 16:34:33 +02:00
Thomas Geymayer
dfdd1c6d5e cppbind: some new methods and safer type check. 2014-05-29 16:30:41 +02:00
Thomas Geymayer
a8e96997cb nasal::Hash: add iterators. 2014-05-29 14:14:52 +02:00
Thomas Geymayer
6db07373ee PropertyBasedElement: extend HTML5 (Canvas) data props interface
Allow check if property exists and removing properties.
2014-05-29 01:22:35 +02:00
Thomas Geymayer
36ae8cdce3 Update doxgen config and some comments. 2014-05-29 00:27:50 +02:00
Thomas Geymayer
d77ba7d2db canvas::Element: setter/getter for HTML5 style data-* properties. 2014-05-28 18:11:32 +02:00
Thomas Geymayer
6e58fdac85 canvas: exclude data-* properties from triggering an update.
Allows using data-* properties on canvas and elements,
similar to HTML5, without triggering a new rendering of
the canvas.
2014-05-27 17:56:46 +02:00
Thomas Geymayer
3f6933940e ShivaVG: get rid of glu dependency.
We do not use the image rendering part anyhow, so
just disable it for now. If we want to use it we
will need a modified implementation anyhow to
integrate with OpenSceneGraph threaded OpenGL
resource management.
2014-05-27 17:56:46 +02:00
James Turner
4bd8fe5855 Fix bad Nasal parse of ‘foo[]’.
When a vector subscript is empty, don’t parse it as ‘[foo]’, instead
print a parse error. 

Fix by Nicholas Scheel & Thomas Geymayer.
2014-05-27 10:05:17 +01:00
Thomas Geymayer
9c421d55a9 Canvas: support for custom events and event dispatching. 2014-05-19 00:17:23 +02:00
Thomas Geymayer
1af6cbc1aa cppbind.Ghost: more member overloads. 2014-05-18 23:55:43 +02:00
Thomas Geymayer
a9e7af6e6e Enable dynamic linking on Mac for Boost.Test 2014-05-18 17:47:03 +02:00
Thomas Geymayer
c967fbc0a6 cppbind.Ghost: clean up a bit 2014-05-18 17:43:30 +02:00
Thomas Geymayer
6929ba75c0 cppbind: convert maps from nasal. 2014-05-18 13:34:06 +02:00
Thomas Geymayer
289777bd99 cppbind.Ghost: register _get called on retrieving unset member.
Allow registering a callback on ghosts called upon retrieving
an unset member.
2014-05-18 13:34:06 +02:00
Thomas Geymayer
7535dfd2d0 cppbind: from_nasal/to_nasal for SGWeakReferenced based objects 2014-05-10 10:49:11 +02:00
Thomas Geymayer
23279b4d0a Canvas: move window from flightgear. 2014-05-09 16:17:40 +02:00
Thomas Geymayer
d34d3ecfb1 SGWeakReferenced: fix for clang. 2014-05-09 11:52:42 +02:00
Thomas Geymayer
55f18574aa Fix for CMake 2.6
No named arguments for add_test in CMake 2.6.
2014-05-08 11:17:28 +02:00
Thomas Geymayer
02ac1a43c1 Add smart pointer tests (finally using Boost.Test) 2014-05-08 02:17:24 +02:00
Thomas Geymayer
b2c3a90adf Fix use count for deleted, reference counted objects.
Remove inconsitency of returning 0 or ~0. If the object
has been deleted there are no more references, so always
return 0.
2014-05-08 01:43:41 +02:00
Thomas Geymayer
9975c751a7 Add support for using SGWeakPtr with virtual base classes.
- Add SGVirtualWeakReferenced to be used as base class
   for reference counted objects supporting weak references
   and virtual base classes.
 - Modify SGWeakPtr to copy with SGVirtualWeakReferenced
   objects.
2014-05-08 00:27:00 +02:00
Thomas Geymayer
efbec8b367 Canvas: base Elements/Canvas on SGWeakReferenced. 2014-05-06 18:10:42 +02:00
Thomas Geymayer
9642f6d946 cppbind: add naContext/naRef overload to Ghost::_set 2014-05-06 18:10:35 +02:00
Thomas Geymayer
247aa49849 SGSharedPtr/SGWeakPtr: add some methods/operators
- allow placing SGWeakPtr in sorted STL containers (eg. requiring
   operator<)
 - add reset() like for boost::shared_ptr/boost::weak_ptr
 - add helper to extract pointer from SGWeakPtr
2014-05-06 18:10:14 +02:00
Thomas Geymayer
edaae885ee cppbind: small logging/warning updates. 2014-05-05 15:06:15 +02:00
Thomas Geymayer
b101f64cd8 cppbind.Ghost: register _set called on setting new properties. 2014-05-05 15:06:09 +02:00
Thomas Geymayer
f75d1cbcb1 cppbind: disable special handling of 'parents' for ghost. 2014-05-05 14:48:29 +02:00
Thomas Geymayer
ac2e80dc07 Nasal: allow ghost as 'me' for 'call' 2014-05-05 14:47:10 +02:00
Peter Sadrozinski
85b17ae8d4 fix for sgGetBuckets. Northern most buckets in the bounding box were left out 2014-04-16 10:18:13 -04:00
Thomas Geymayer
77955e5c3c math/nasal: Add more SGRect members and nasal helper. 2014-04-15 10:07:01 +02:00
Peter Sadrozinski
19481983e5 fix windows build 2014-04-06 12:14:44 -04:00
Peter Sadrozinski
5b2b420c48 - secondary tc and vertex attribs in .btg file
- decrease debug when writing btg files
- deprecate some set_xxx apis
2014-04-05 21:48:38 +02:00
James Turner
15d3c12139 Fixed for FreeBSD
(From Gerald Laplanche)
2014-04-03 13:42:11 +01:00
Thomas Geymayer
338a748823 Canvas::Path: also restore glBlendFunc. 2014-04-01 12:35:43 +02:00
Thomas Geymayer
8c45796dc8 Canvas::Path: reduce number of OpenGL state changes.
- Do not enable any multisampling as for FBOs it is not
   supported anyhow.
 - Just restore GL_BLEND and GL_STENCIL_TEST manually
   => Getting rid of the huge load of glPushAttrib/glPopAttrib
      gets us a huge performance boost, especially for drivers
      with slow state stack implementations.
2014-04-01 12:03:53 +02:00
Thomas Geymayer
a160e176da cppbind: fix testing for naRef identity. 2014-03-31 14:21:21 +02:00
Thomas Geymayer
d332da0605 Canvas: do not write bounding box to property tree.
- Prevent writing to property tree in wrong thread.
 - Add Element::getBoundingBox and
   Element::getTightBoundingBox as uniform way to
   retrieve bounding boxes of all canvas elements.
 - Update path bounding boxe in update traversal.
2014-03-31 13:28:03 +02:00
Thomas Geymayer
6ea55c6851 nasal::Ghost: remove bogus warning. 2014-03-27 12:09:05 +01:00
Thomas Geymayer
10ee7a901f ShivaVG: disable paint textures (Fixes #1414)
Currently there is no need for paint textures so just
disable them to prevent releasing OpenGL resources
from the wrong context/thread.
2014-03-27 11:20:38 +01:00
Thomas Geymayer
db8b60ec49 Revert "CanvasGroup: return empty bounding box without children."
Returning somehow valid data in case it is used unchecked was
probably not a good idea. Better fix the code using it^^

This fixes #1421.

This reverts commit 3525fff8d0.
2014-03-23 17:27:30 +01:00
Thomas Geymayer
09d47e7f55 cppbind: fix problem with function call without arguments. 2014-03-22 13:23:21 +01:00
Thomas Geymayer
13a3ea3503 cppbind: allow calling methods with 'me' object from C++. 2014-03-22 12:41:47 +01:00
Thomas Geymayer
ff53792e4f CanvasImage: rename 'file' to 'src'. 2014-03-20 11:42:02 +01:00
Thomas Geymayer
fd51518d92 CanvasImage: improve fetching from http and add mime detection. 2014-03-20 01:44:31 +01:00
Thomas Geymayer
01a43b49a5 Canvas: Respect clipping while event handling. 2014-03-19 18:21:25 +01:00
Thomas Geymayer
3525fff8d0 CanvasGroup: return empty bounding box without children.
Return an invalid bounding box can lead to problems
if used unchecked. Just returning an empty bounding
box (size 0x0 and position at the origin of the
element) is much safer.
2014-03-19 16:31:17 +01:00
Thomas Geymayer
6c75e2fe3c CanvasImage: support for http protocol.
- Download and display images.
2014-03-18 18:33:44 +01:00
Mathias Froehlich
b322864ef4 Fix detection of airports in the stg loader. 2014-03-16 16:08:31 +01:00
Thomas Geymayer
f28e3fc9bb Canvas: fix position for drag events and multiclick handling.
- Calculate local/client position for every drag event
   instead of just reporting the position of the initial
   mousedown event.
 - Only pressing the same button multiple times increases
   the mouse clickcount.
2014-03-15 14:53:08 +01:00
Thomas Geymayer
80e77b8372 Canvas: set button for drag events. 2014-03-15 00:40:48 +01:00
James Turner
c925b7b601 Don't leak cube-map textures
- use observer_ptr so cube-map textures are released without the
builder holing a ref.
2014-03-13 22:16:58 +00:00
James Turner
966789de90 Fix leak of object-mask textures 2014-03-13 22:16:00 +00:00
Torsten Dreyer
d44a3117d9 Partial fix of #1408, add missing throw 2014-03-12 21:10:14 +01:00
Thomas Geymayer
c851c449da cppbind: Do not derive from SGReferenced if there is no need 2014-03-12 17:40:21 +01:00
Thomas Geymayer
b2d9385f46 cppbind: fix possible usage of expired object from Nasal 2014-03-11 15:40:26 +01:00
James Turner
f40efe66fa Simplify copyProperties
- with initial-state save/restore gone, all uses of
copyProperties are now simple. For selective copying, use
copyPropertiesWithAttribute.
2014-03-11 08:32:33 +00:00
Thomas Geymayer
5560479665 SGPropertyNode: do not keep list of deleted children.
Get rid of the possibilty to keep delted children. This was not
used actively, required unnecessary memory for each property node
and lead to subtile bugs, as creating a node after a node with
the same path has been deleted resulted in getting the old node
back instead of a cleanly initialized one.
2014-03-06 00:39:23 +01:00
James Turner
4ea8e4774e Remove unused cube-map from 2D clouds. 2014-03-05 14:42:29 +00:00
James Turner
18ff03acdf VBO switch also controls random-buildings. 2014-03-05 10:20:48 +00:00
Thomas Geymayer
aa27a09801 MouseEvent: comply with DOM Level 3 and add some getter. 2014-03-04 17:58:37 +01:00
James Turner
a8d7645004 Code cleanups.
(no functionality change)
2014-03-04 16:05:36 +00:00
James Turner
081aafb914 Allow clouds to be toggled on/off
- Change the cloud-root Group to a Switch
2014-02-28 11:06:47 +00:00
Stuart Buchanan
5a5ba08c23 Fix longstanding rent in skydome under ALS. 2014-02-27 21:57:27 +00:00
Thomas Geymayer
91b92c5613 animation: fix for 'textrapezoid' animation outside 'texmultiple'. 2014-02-26 17:48:11 +01:00
James Turner
bd5bc9cda0 TerraSync: expose method as public
- needed for command-line terrasync util.
2014-02-25 09:13:16 +00:00
James Turner
db684eb33e HTTP: improve handling of connection errors
- handle 'whole connection' errors consistently, e.g. EUNREACH
which can occur with intermittent connections.
2014-02-24 19:41:14 +00:00
James Turner
a07cfdb683 Remove obsolete TerraSync methods.
- all scheduling is now done via paths/buckets, not lat+lon.
2014-02-24 18:45:57 +00:00
James Turner
b956a2c834 Sky: expose OSG branches as Groups.
- avoids creating additional groups in Renderer
2014-02-24 18:45:57 +00:00
James Turner
ba38688a83 Fix failing BucketBox test
- improve behaviour of floorWithEpsilon helper in bucket code, and
use in more places so that near-integral values are rounded correctly.
2014-02-24 18:03:17 +00:00
Stuart Buchanan
3429e00721 Add osgUtil::Simplifier to allow terrain simplification. 2014-02-23 21:09:09 +00:00
James Turner
32a6bd78d8 Make return type from loadPagedModel explicit.
- explicitly return osg::PagedLOD instead of Node, so callers
don't need to rely on documentation + cast to get the LOD
settings on the result.
2014-02-22 16:24:47 -08:00
Thomas Geymayer
b2cedc5332 Canvas: Fix crash on hide/show after detaching element from scenegraph. 2014-02-22 01:51:54 +01:00
Christian Schmitt
94bbed80d0 Use the new SGBucket api 2014-02-21 17:38:26 +01:00
Christian Schmitt
b4ecb6f2db Fix new SGBucket API 2014-02-21 11:13:32 +01:00
James Turner
3b5665fe92 Only disable display lists if using VBOs.
(Otherwise we fall back to very slow submission)
2014-02-19 15:10:48 -08:00
James Turner
b4f4ef9c5b Optionally deprecate parts of SGBucket
When NO_DEPRECATED_API is set, remove portions of the
SGBucket API.
2014-02-19 14:06:48 -08:00
James Turner
3c4c05fb3e Fix GCC 4.6 / Ubuntu compile failure. 2014-02-19 14:05:26 -08:00
James Turner
b0cad59508 Raise required OSG version to 3.2.0 2014-02-19 10:58:39 -08:00
James Turner
d4030e72e4 Runtime VBO option in BTG loading. 2014-02-18 19:58:19 -08:00
James Turner
481f7e3bfa Technique selection: fix shader-language
(Also add a glsl-supported test to simplify Effect logic)
2014-02-18 19:56:24 -08:00
Stuart Buchanan
c35243e215 OSG 3.2.0 compatibility and surface light effects. 2014-02-18 21:30:15 +00:00
James Turner
00d8849a28 Revise SGBucket::get_width_m
- remove the 0.5 degree offset away from the equator, which was
  causing wrap-around at the poles
- add a new helper to get the highest latitude of a tile (which may
  be +/- 90 for the polar cap tiles), and use this to compute radius
- special case the polar-caps and return a fixed, small width so
  the tile-manager loads all tiles.
2014-02-13 18:49:38 +00:00
Thomas Geymayer
6b9ce935cd Write back additional attributes to PropertyList 2014-02-13 18:27:34 +01:00
James Turner
e440aba0d2 Some Linux platforms need <cstdio> for snprintf. 2014-02-13 15:31:58 +00:00
James Turner
574d459f4e Fix MSVC build. 2014-02-13 13:23:43 +00:00
James Turner
5fa0931a89 Revised set_bucket implementation.
- sink some inline methods into the .cxx file
- add isValid test to SGBucket
- sgGetBuckets won't return invalid buckets near the poles
- additional unit tests
2014-02-13 12:36:06 +00:00
James Turner
748b13850a SGBucket unit-testing
- test polar wrapping near the 180-meridian
- test corner points for polar tiles
- expand test-macro helpers slightly
2014-02-12 16:21:16 +00:00
James Turner
9a09a6a447 Add new sibling() helper to SGBucket
- will replace sgBucketOffset eventually
- expand test coverage for both sibling() and offset function

(one test case is disabled since it fails)
2014-02-12 15:31:40 +00:00
James Turner
79c56c924f Add unit-testing for SGBucket
- as part of looking at polar behaviour, start testing the
bucket code since the code has subtleties which it would be unwise
to change.

- remove a legacy setter on SGBucket (taking two doubles as a C-array)
2014-02-11 21:49:45 +00:00
James Turner
eef73953cb Race-condition fix in get_effect.
Adapted from a fix by Jeff Biggs. (Shows up easier when using osgEarth,
and multiple osgDB pager threads)
2014-02-10 17:59:02 +00:00
Thomas Geymayer
781682f4ee Minor formatting... 2014-02-08 19:15:46 +01:00
Thomas Geymayer
4a10806352 SubsystemMgr: helper to remove all subsystems. 2014-02-08 19:15:14 +01:00
Thomas Geymayer
13ae2f08ee Put unused attributes in the property tree while parsing.
Store unhandled xml attributes in a subnode named _attr_ for each
node on parsing propertylist files. This allows retrieving them at
a later stage where there is no access to the source xml file any
more.
2014-02-08 19:12:17 +01:00
Thomas Geymayer
3e8aeccc87 New textrapezoid TexTransformAnimation.
Allows trapezoid texture transformations, as needed for example to
correct for the tilt angle of HUD combiners.
2014-02-08 12:39:43 +01:00
Thomas Geymayer
de39e0093a Canvas: Fix bounding box calculation for paths with subpaths. 2014-02-08 00:22:36 +01:00
Thomas Geymayer
943f52aedf Canvas::Element: expose combined transformation matrix 2014-02-06 17:55:51 +01:00
James Turner
46eb2f439d MSVC lacks stdint.h / inttypes.h
- we can't use simgear/misc/stdint.hxx here, since it's a C++
header and md5.c is compiled as plain C (no bool type available).
2014-02-06 12:10:34 +00:00
James Turner
4f5c519636 Replace md5 from RSA with OpenBSD version
- no more BSD-with-advertising-license
- files from http://sources.debian.net/src/dpkg/1.17.6/lib/compat
(with SG_ prefixes to avoid collision issues)
2014-02-06 10:42:41 +00:00
James Turner
8f6677974e Mac: fix static linkage
- Link against Cocoa, not CoreServices (for NSFileManager)
2014-01-28 12:39:28 +00:00
James Turner
0f5cbc35a2 Apple directory helpers
- drop use of deprecated FSFindFolder (use NSFileManager)
- support Downloads and Pictures directories
2014-01-28 11:16:19 +00:00
Thomas Geymayer
c54b1e037a SGPath: typo... 2014-01-28 00:42:20 +01:00
Thomas Geymayer
0d19be2196 SGPath: generalize retrieving standard locations (and add more) 2014-01-28 00:32:08 +01:00
James Turner
c1b579ec5d Remove jpeg-factory.
(FlightGear can now do the same, without the helper class)
2014-01-27 09:19:11 +00:00
James Turner
0a8246c676 Add desktop() accessor to SGPath
(Windows-only for the moment)
2014-01-26 16:03:31 +00:00
Torsten Dreyer
ea02453f63 fix typo
Thanks to Alex Romosan
2014-01-24 11:10:34 +01:00
James Turner
d1942da317 Remove some debug log messages
- two uninformative ones in TextureBuilder
- very noisy one in tied-property-list
2014-01-23 13:51:18 +00:00
James Turner
ed9764f923 Fix spurious event-mgr warnings
- don't warn on removeTask if the event-manager was already shutdown,
  as happens during normal FG shutdown or reset.
2014-01-23 12:49:39 +00:00
James Turner
3b5ed81216 Crash-fix: make mat-lib reference counted.
Make SGReaderWriterOptions own the material lib it's passing to
loader code, so it cannot be freed unexpectedly.
2014-01-19 16:45:01 +00:00
Torsten Dreyer
12d0d4140e Version 3.1.0 2014-01-17 21:59:58 +01:00
Torsten Dreyer
d6c8008978 No double-zero for the version number :-/ 2014-01-17 21:49:13 +01:00
James Turner
488ca0fcc5 Remove a noisy log message from the Terrasync code. 2014-01-17 17:46:46 +00:00
James Turner
dfd15cadab Bug-fix - short-circuit path validation.
When an SGPath is empty, don't bother stat()-ing. This avoids
an uninitialized-memory read inside the C-runtime on Windows.
2014-01-17 17:26:29 +00:00
James Turner
1613257bdc Ensure type tag of contextId binding is set.
(Otherwise defaults to DOUBLE).
2014-01-17 16:27:54 +00:00
Torsten Dreyer
1061580118 Version 3.0.0 2014-01-16 21:47:13 +01:00
James Turner
7184828a58 Support a 'clear' operation on SGBinding.
Needed to bug-fix a problem with the menubar on Mac - post-3.0 I will
change the behaviour of SGBinding's destructor to avoid the property
copies in other use-cases.
2014-01-15 21:56:26 +00:00
Thomas Geymayer
0428adff03 CanvasMap: Expose property screen-range.
Only allowing a hardcoded screen range of 200 units
is not very flexible...
2014-01-09 19:36:38 +01:00
James Turner
04246f0a63 HTTP: tweak response parsing.
Signal a parse failure via an exception, instead of via a status
code, since this is hard to disambiguate from a valid server-side
status code.
2014-01-07 16:22:10 +00:00
James Turner
61df58d651 Terrasync: update fixes.
- Fix a nasty issue when updating existing repositories, which could
cause repository corruption and hence repeated re-syncs.
2014-01-07 14:24:01 +00:00
James Turner
1bd9a440e8 HTTP: tweak for malformed header handling. 2014-01-07 14:24:01 +00:00
Thomas Geymayer
d82c8eb945 Socket: do not crash on writing to broken pipe.
Writing to a closed socket on all Unix/Linux systems raises
a SIGPIPE signal which immediately closes the program. Disabling
raising of SIGPIPE allows proper error handling by checking the
return values of send/sendto and getting the EPIPE in errno.
2014-01-06 12:17:59 +01:00
James Turner
becbad7ea4 Crash fix: use cache to find materials.
Add a thread-safe cache of the currently valid materials, to avoid
the osgDB thread making repeated SGCondition evaluations in conflict
with the main thread.

(Requires an accompanying FG change)
2014-01-06 08:26:48 +00:00
Thomas Geymayer
2335796a28 Fix track-to animation basic tracking without slave object 2014-01-05 19:38:03 +01:00
James Turner
c6330b64f6 Bugs 1207, 1301
Only apply the fix for time-zone offsetting on Windows, since Unix
is handling mktime differently. (Arguably we should also apply a
conversion for Unix systems, but the previous logic worked)
2014-01-03 19:51:58 +00:00
James Turner
df57a23512 #1207: Add helper to get Unix time in GMT
On Windows, time() is returning a value which if offset by the
selected time-zone. Provide a variant which is always in GMT.
2014-01-01 21:03:24 +00:00
Thomas Geymayer
aea71cf0af HTTPFileRequest: only save file if status == 200 2013-12-30 01:31:47 +01:00
James Turner
fb54386f8d Bugfix: remove SGSky minimum distance check.
Without this, various sky rendering modes exhibit artefacts in low-
visibility conditions. The cost of always rendering the dome is 
negligible. (And Rembrandt already had to work this way)
2013-12-19 17:20:06 +00:00
Rebecca Palmer
4d931b6109 Fix test failure on char-unsigned-by-default architectures (eg. ARM)
(from http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=722115 )
2013-12-19 15:02:07 +00:00
Rebecca Palmer
407c7a10f5 Fix buffer overflow CVE-2012-2091 (thanks to Saikrishna Arcot)
https://bugs.launchpad.net/ubuntu/+source/simgear/+bug/1077624
(discussed in comments 65-78)
(This is already fixed in 2.12 but appears to have been forgotten in 2.99;
the other vulnerabilities described there are already fixed.)
2013-12-19 15:02:07 +00:00
James Turner
16fa4d1d17 Security: fix 0777 directory permissions to 0755. 2013-12-19 14:56:23 +00:00
James Turner
1ecccd43d4 Fix a typo breaking Mac build. 2013-12-15 20:55:02 +00:00
James Turner
2edbb1cf8b Reset: clear some property references. 2013-12-15 20:48:30 +00:00
Thomas Geymayer
8dccbe8029 SGPath: some tweaks/fixes/typo on permission checker. 2013-12-12 21:27:27 +01:00
Thomas Geymayer
031c833e4d SGPath: add support for custom PermissionChecker
Allow to add permission checks (read/write-access) for
eg. limiting access based on IORules.
2013-12-12 21:04:26 +01:00
James Turner
890cc46825 Reset: alternate property copying strategy.
Search the tree for leaves / sub-trees with a particular attribute set.
This permits both sparse selection as well as selection by sub-tree.

(This is needed for PRESERVE attribute, and once new reset is switched
on, will enable 'normal' copyProperties to revert to ignoring
attributes completely)
2013-12-10 17:37:37 +00:00
James Turner
6b0f7e8512 Quiet a message. 2013-12-09 23:28:52 +00:00
Thomas Geymayer
7a220921f0 Canvas: one global SystemAdapter is enough.
This also fixes elements requiring a SystemAdapter without
being assigned to a Canvas (eg. because they are desktop
elements).
2013-12-09 23:07:17 +01:00
Thomas Geymayer
7ef9acda5d Add ListDiff helper (for fast array differences). 2013-12-08 18:48:57 +01:00
Thomas Geymayer
2a6d62e6f7 Fix some unused/uninitialized variables. 2013-12-07 13:43:17 +01:00
Thomas Geymayer
42b50bd7f7 SGPropertyNode: add removeAllChildren() method 2013-12-07 13:42:15 +01:00
Thomas Geymayer
2c3f44ae11 Canvas: ensure all canvasses are destroyed
- For shutdown/reset make sure all properties describing a canvas
   are removed and also not restored after the reset.
2013-12-07 13:40:22 +01:00
Stuart Buchanan
b7662f16e5 Work around apparent OSG 3.2.0 normal binding bug. 2013-12-06 22:12:52 +00:00
Stuart Buchanan
5bc535a8d2 PagedLOD for random trees, buildings and objects. 2013-12-06 20:20:46 +00:00
James Turner
3ec9c7ae6e Canvas: clear elements in shutdown()
- without this change, elements aren't un-ref-ed and hence destroyed
until the actual dtor runs, which is too late (other systems may be
destroyed already)
2013-12-06 18:14:48 +00:00
James Turner
e326d0c756 Work around a clang/libc++ issue. 2013-12-06 18:03:35 +00:00
Thomas Geymayer
7c5434a99f cppbind: don't leak nasal references for Ghost methods. 2013-12-04 11:15:11 +01:00
James Turner
26d9256ee9 Reset: hook to clear the effects cache. 2013-12-04 09:12:51 +00:00
James Turner
053e761d8f Reset: don't leak EffectPropertyListener.
Track and free listeners from effect expressions.
2013-12-03 21:36:18 +00:00
Clément de l'Hamaide
5861b52cff Implement UTF-8 to Latin1 converter 2013-12-01 18:48:05 +01:00
Thomas Geymayer
66193d7283 cppbind: Fix detecting derived Ghosts 2013-12-01 12:35:02 +01:00
Thomas Geymayer
dfb07f359e cppbind: make ghost_type static to keep same pointer on reset.
Using a static naGhostType ensures that always the same
address is used for the same ghost and prevents ghost_types
from getting invalid (eg. on reset).
2013-12-01 01:18:13 +01:00
Thomas Geymayer
6fe1433497 Canvas: allow removing placement factories. 2013-11-24 15:39:03 +01:00
bcoconni
01ace109ba Fixed a crash: the singleton needs to be instantiated the first time SGCommandMgr::instance() is called 2013-11-24 14:50:11 +01:00
bcoconni
8e01acaa12 Added more explicit error messages to the property lists parser.
More specifically, each time an error or a warning is issued the location of the corresponding XML tag (file name an line number) is reported.
2013-11-24 12:20:26 +01:00
James Turner
7bbec8dbcb Reset: allow re-init of Nasal Ghosts.
After discussion with Thomas, reset needs the scenario this assert
was designed to guard against, so removing it for now.
2013-11-23 20:06:47 +00:00
James Turner
10375086ed Allow Command-manager singleton to be deleted.
(Shutdown can delete commands in an orderly way)
2013-11-23 20:00:23 +00:00
James Turner
dd367daac9 Fix another SVN crash on failure. 2013-11-19 21:33:50 +00:00
Martin Spott
27063d0c32 Repository name preferred 2013-11-16 10:44:17 -08:00
James Turner
80ff0282c3 Give terrasync a custom HTTP UA. 2013-11-16 09:45:40 +00:00
James Turner
e973e406a3 Fix some SVN errors/crashes. 2013-11-15 11:47:36 +00:00
bcoconni
c64776029e Removed the 'using::std' instructions from the headers as per James Turner request.
Also took this opportunity to add a check in XMLVisitor::savePosition() against a null pointer.
2013-11-14 22:07:29 +00:00
bcoconni
5d7d0a922d Added the ability to access the file name and position (line, column) from which the XML visitor is getting its data 2013-11-14 22:07:29 +00:00
Christian Schmitt
c2c96c4219 apt_signs: lower the log level for apt.dat syntax errors. Nobody seems to care... 2013-11-14 18:41:20 +01:00
James Turner
3e9ed08c53 Reset: use ref-counting to own subsystems.
Change the subsystem-group and manager code to use shared-ptr references
to subsystems, instead of holding a raw pointer. Hence the manager becomes
the owning ref to most subsystems.
2013-11-12 23:12:45 +00:00
James Turner
b7df9026c0 Reset: Terrasync root can change, can be shutdown. 2013-11-12 23:04:13 +00:00
James Turner
6527c6357b Reset: clean event-manager shutdown 2013-11-12 22:23:19 +00:00
James Turner
fad333256c Reset: DeletionManager can be uninstalled 2013-11-12 22:23:14 +00:00
James Turner
8753811fe1 Remove exceptions on missing texture names 2013-11-12 22:23:09 +00:00
James Turner
237752ff62 String case conversion, UTF-8 conversion. 2013-11-12 22:17:12 +00:00
Thomas Geymayer
743244a69c CanvasElement: ensure parent pointer can not become invalid. 2013-11-10 17:12:50 +01:00
Thomas Geymayer
9f89737986 track-to animation: fix "short" and "long" tracking. 2013-11-10 11:14:50 +01:00
Thomas Geymayer
508a5c5f66 track-to animation: 2dof rotation for slave object.
Slave objects can now rotate around two axis to always track the
target - even when it is outside the plane perpendicular to the
lock-axis of the root object. This allows for example animating
levers with non-parallel axis of rotation connected with a rod.
2013-11-08 00:51:18 +01:00
James Turner
d84394efa0 Tweak HTTP code to always sleep.
Check explicitly for the 'no channels' case and
sleep instead, to avoid busy-waiting. (This is a work-
around, the underlying issue still be be traced)
2013-11-06 15:11:46 -08:00
James Turner
dd613d48bc Reset: pin ctor and dtor of event manager. 2013-11-05 05:21:57 +00:00
James Turner
2e8e500d4f Reset: commands can be removed 2013-11-05 05:21:52 +00:00
James Turner
48f866dcc6 TerraSync: avoid assert on missing file 2013-11-05 05:17:57 +00:00
Thomas Geymayer
ec4d3e4f9b Make new gcc happy (include cmath) 2013-11-04 10:48:26 +01:00
Thomas Geymayer
38ddfab1e0 Canvas: improved clipping and new property clip-frame.
- Update clipping rect if canvas view or texture size
   changes.
 - Add new property "clip-frame" to specify coordinate
   frame for "clip" coordinates. Coordinates can be
   global, relative to the parent or relative to the
   element itself.
2013-11-03 20:12:45 +01:00
Thomas Geymayer
353b7e4438 SGPropertyNode: extract enum value with getValue 2013-11-03 20:11:06 +01:00
Thomas Geymayer
7076c9a0ff HTTP: finish core request before calling any callback. 2013-10-28 20:34:11 +01:00
James Turner
2f42a8714c Terrasync: don't busy wait when no requests are running. 2013-10-28 14:51:52 +00:00
Thomas Geymayer
fdf6fc32ff HTTPFileRequest: create directory if it does not exist 2013-10-28 12:38:39 +01:00
Thomas Geymayer
f2188b33c6 HTTP: Rename urlretrieve/urlload to save/load. 2013-10-27 23:38:46 +01:00
Thomas Geymayer
f93fead8f2 io: refactor and improve HTTP modules.
- refactor code used multiple times spread over sg/fg into
   one single location.
 - allow aborting requests.
 - Provide two common request types:
  * FileRequest: Save response into file
  * MemoryRequest: Keep resonse in memory (std::string)
 - extend HTTP::Client interface:
  * urlretrieve: Save url to file (shortcut for making a
                 FileRequest)
  * urlload: Get respons into memory (shortcut for making
             a MemoryRequest)
2013-10-27 19:05:49 +01:00
Thomas Geymayer
050f3791cc New class simgear::Map extending std::map interface. 2013-10-27 19:05:49 +01:00
James Turner
94326fd964 Windows logging changes
- can log to the debugger (MSVC output window) via OutputDebugStr
- logging to the console is optional, triggered by new
  requestConsole hook()
2013-10-26 21:04:16 +01:00
James Turner
f26f3b606d Fix handling of SGPath::rename
Windows handling of ::rename differs from POSIX when the new name
exists. Check for this case on Windows and remove the file manually.
(This allows a work-around in SVNDirectory to be removed)
2013-10-26 21:03:00 +01:00
Thomas Geymayer
7cbfa76be4 cppbind: automatic conversion of SGReferenced derived pointers. 2013-10-26 01:04:16 +02:00
James Turner
d896e71ae9 Add total bytes downloaded tracking. 2013-10-24 23:35:44 +01:00
Thomas Geymayer
ca79d99ec4 canvas::Element: add getter for parent element. 2013-10-23 17:11:03 +02:00
James Turner
95f77ef81d Ooops, fix INSTALL rule.
We need the rule, just not the export, for now.
2013-10-23 14:18:39 +01:00
James Turner
c4e7d26b70 Comment out Cmake export experiments for now.
This is breaking CMake 2.6, so commenting out for the moment, until
I understand how to support this portably.
2013-10-22 13:42:59 +01:00
James Turner
ea49a1a07b Fix GPS activation on Windows.
Bug 1190, fix route-manager -> GPS activation on Windows
(and probably a couple of unrelated issues).
SGPropertyChangeCallback was missing a suitable copy-constructor,
leading to incorrect behaviour on compilers where the operation
is not elided. Such as MSVC.
2013-10-22 00:42:31 +01:00
James Turner
784223c67f Fix Dir warnings on Windows.
Test result of GetLastError so we don't log a warning in the
'nothing matched' case. Makes early startup logging much
more pleasant, especially when scanning aircraft dirs.
2013-10-21 23:51:19 +01:00
James Turner
77aa2c9a9d Smoother download rate from HTTP
Crude filtering (low pass) of the download rate, over a 400msec
time window.
2013-10-21 23:08:55 +01:00
James Turner
48145fb234 Fail HTTP request with missing/illegal protocols.
Instead of throwing, run the failure handler for requests with
either a missing spec or something other than http:// (eg, https
or ftp)
2013-10-20 23:20:49 +01:00
James Turner
49730cadee Placement can contain multiple nodes.
- Allow placement to have multiple children added.
- Remove legacy position setting APIs, enforce use of SGGeod
2013-10-20 20:44:42 +01:00
Thomas Geymayer
0b197501e1 Add SGPropertyNode::setValueReadOnly.
Helper to set value of relative node and mark
read only. Use eg. for exposing configuration
and build values to the property tree.
2013-10-20 12:03:41 +02:00
James Turner
8b0c246a7b Avoid a divide-by-zero on malformed BTG files.
Encountered while testing v850 airports; some airports
generate materials with no tris, and hence no indices.
This causes a divide-by-zero when computing the index stride.

Detect this, and convert the BTG reader to throw exceptions
in error conditions, and to catch this and report the
appropriate result code.
2013-10-17 20:13:50 +01:00
James Turner
9c4face1ae Fix float-int conversion bug caught by Clang 2013-10-17 20:13:49 +01:00
James Turner
952e2e6631 Fix unused-var warnings from Clang. 2013-10-17 20:13:49 +01:00
James Turner
4766910413 Terraysnc: don't block requests when inactive.
Add the same check for data files so we don't block when terrasync is
disabled.
2013-10-17 17:29:06 +01:00
James Turner
3c2f97a8d2 Re-order install(EXPORT ) lines.
Certain cmake builds/versions complain when a dependent target is
exported but dependencies are not. Export SimGearCore before
SimGearScene.
2013-10-17 17:12:02 +01:00
James Turner
f75730f165 Initial work on syncing non-scenery data.
This is some initial pieces to synchronise other pieces of base
data than scenery via the sync mechanism. An additional sync slot is
added to avoid scenery or other data blocking each other.
2013-10-17 16:39:29 +01:00
James Turner
b596e62a61 Remove some iostream debugging.
Accidentally committed this debug stuff, remove it.
2013-10-17 16:35:36 +01:00
James Turner
1349d48339 Export SimGear config to cmake.
Experimenting with this, should make FindSimGear module unecessary,
and avoid mis-matched SG/FG cmake settings in the future.
2013-10-17 16:10:18 +01:00
James Turner
d69ea5fda6 Kill off EMBEDDED_SIMGEAR 2013-10-17 16:09:39 +01:00
Thomas Geymayer
9b68062ba7 Add helper to get osg::Node path as string. 2013-10-17 13:28:54 +02:00
Thomas Geymayer
b217019723 Nasal: do not print invalid elements for error backtraces. 2013-10-16 23:33:06 +02:00
Thomas Geymayer
a37a254a68 Canvas: simplify code by using new nasal function conversion. 2013-10-15 17:46:42 +02:00
Thomas Geymayer
5258699f20 cppbind: convert Nasal functions to C++ boost::function. 2013-10-15 17:46:31 +02:00
Thomas Geymayer
483bebb003 cppbind: fix bad class hierarchy for bad_nasal_cast. 2013-10-15 16:38:38 +02:00
Thomas Geymayer
8ca8052a8d Nasal: use correct function pointer in naCall (with user data) 2013-10-15 14:21:45 +02:00
Thomas Geymayer
d68b1144b8 Nasal: recursive method calling.
New functions naCallMethodCtx and naCallMethod to replace
NasalSystem::callMethod from FlightGear. This has just added an
unneeded level of indirection and fits better directly into Nasal.
naSetErrorHandler can be used to register an error handler/logging
function.
2013-10-15 12:08:42 +02:00
Thomas Geymayer
42c39b6be3 Canvas: use new naGCSave/naGCRelease functions. 2013-10-15 00:49:13 +02:00
Thomas Geymayer
bb82b9d168 Nasal: add naGCSave/naGCRelease for preventing objects being garbage collected.
These new functions are meant to replace the gcSave/gcRelease
methods of the NasalSystem class in FlightGear, as passing an
adapter to SimGear from FlightGear is quite a lot of useless work
just for being able to save objects.
2013-10-15 00:19:32 +02:00
Thomas Geymayer
b1f865d461 cppbind: add to_nasal_helper for enums. 2013-10-14 23:03:36 +02:00
Thomas Geymayer
8e75c6be50 HTTP: check url for scheme and report instead of failing with assert. 2013-10-13 12:06:17 +02:00
Thomas Geymayer
06a5f9188d Don't let exceptions escape from commands. 2013-10-13 12:00:52 +02:00
Thomas Geymayer
370a991208 cppbind: Catch exceptions before reaching C code. 2013-10-13 11:40:27 +02:00
Thomas Geymayer
6deb77dd4d Canvas: allow also C++ callable entities as event callbacks. 2013-10-12 00:29:37 +02:00
James Turner
9e3172cb04 Another attempt to make MSVC happy!
C89 requires variable declarations upfront.
2013-10-09 12:26:29 +02:00
James Turner
426c6b9a72 Linux needs <cstring> 2013-10-09 11:58:51 +02:00
James Turner
d148bdedcd Fix MSVC build, no C99 so no round().
(Doesn't use SGMisc<T>::round, since Nasal is pure C)
2013-10-09 11:52:54 +02:00
James Turner
23140e3bf7 Avoid a data copy decoding base64 data.
Use an out parameter to avoid a buffer alloc/copy/free cycle
when returning base64-decoded data.
2013-10-09 10:03:30 +02:00
James Turner
2f023803e7 Refactor HTTP content-encoding support.
Move content-encoding handler into its own file, which 
simplifies the main code. As part of this, fix a bug where we
didn't flush the ZLIB buffers on response completion.
2013-10-09 10:03:28 +02:00
James Turner
d658b5fc38 Use std::string.append, avoid string copying.
Removes a hot-spot on the SVN update path.
2013-10-09 10:03:27 +02:00
James Turner
68cd84c330 Make base64 decode significantly faster.
Use a reverse lookup table instead of a call to string.find for
each input byte, much speedier.
2013-10-09 10:03:27 +02:00
James Turner
0bd82a43d3 Check for DAV status parse failures. 2013-10-09 10:03:26 +02:00
James Turner
7fdf42b699 Reset: Nasal additions to force GC, clear saved refs. 2013-10-09 09:59:50 +02:00
James Turner
aeb0e9aac3 Reset: event manager can be unbound. 2013-10-09 09:59:40 +02:00
James Turner
1099a3bdf0 Reset: model placement can drop OSG nodes. 2013-10-09 09:59:33 +02:00
James Turner
aa3458f69c Reset: model lib can drop static root. 2013-10-09 09:59:21 +02:00
Stanislaw Halik
0186cbb7b7 cmake: prevent Boost barfing out too many warns 2013-10-03 21:49:38 +01:00
Stanislaw Halik
fa36e94c4b fix static build with recent GNU binutils
Signed-off-by: Stanislaw Halik <sthalik@misaki.pl>
2013-10-03 21:49:38 +01:00
James Turner
ad83e70cf5 Extend built-in Nasal math.
- rename mod() to fmod() to prevent collisions with the
version in math.nas (which has different handling of negatives)
- implement pow, tan, acos and asin natively
- add round(x, [p]), which rounds away from 0.0, and takes an
option precision. I.e you can round(479, 50) and get '500'; useful
in many digital cockpit displays.
2013-10-03 17:40:17 +01:00
James Turner
4a0377c0a1 Rename WaitingSyncItem -> SyncItem.
More accurate name (we have these for items which are not waiting),
and also shorter.
2013-09-30 21:59:18 +01:00
James Turner
83a3241830 Fix a signed/unsigned compare warning 2013-09-30 16:44:04 +01:00
James Turner
f299b351e3 Expose active tiles via API, sync by SGBucket.
Provide an API to query which tiles are currently being synced. This
allows the higher levels (TileManager in FlightGear) to safely wait on
TerraSync before loading tiles.

Doing this exposed some bugs in scheduling tiles to sync by integer
lat/lon, related to round-towards-zero with -ve values (western /
souther hemispheres). To resolve these, and also reduce spurious
syncs, switch to an explicit API for requesting tiles by SGBucket.
This keeps TerraSync fully aligned with the TileManager queue, which
has many benefits for both high and low visibility situations.
2013-09-30 16:35:45 +01:00
James Turner
3880e4ef47 Say which ocean tile we generated. 2013-09-30 12:08:10 +01:00
James Turner
f205e918d9 sprintf -> snprintf in bucket code. 2013-09-30 12:07:57 +01:00
James Turner
42b0b9306b Make code quieter at log-level=DEBUG. 2013-09-28 14:45:30 +01:00
James Turner
483659c319 HTTP: adjust request-connection assignment.
Prefer existing, idle connections to creating new connections,
even when below the max-connection limit. Gives much better re-use
and pipeline-ing, and hence reduced setup time/trips.
2013-09-28 14:03:39 +01:00
James Turner
add14dd27c Reposition hook for terrasync.
When re-init-ing the sim, tell TerraSync what's going on (we don't want
to actually reset it, since that would disturb in-flight downloads).
Add an explicit method so it can reset its last lon/lat, and hence do
the initial sync at the new location correctly.
2013-09-27 20:28:43 +01:00
James Turner
61516a5e97 Terrasync: Expose transfer rate as a property. 2013-09-27 18:23:51 +01:00
James Turner
17418039e1 HTTP bugfix + enhancement.
Track 'waiting for response' state explicitly, so we don't
falsely report IDLE when waiting for a response.

Expose bytes-per-second download rate as metric.
2013-09-27 18:20:03 +01:00
James Turner
25cae61211 Allow TerraSync to be inited early.
Add explicit subsystem state tracking so we can safely init()
terrasync very early during startup, and hence overlap lengthy operations
(Shared Models sync and initial NavCache build)
2013-09-26 12:11:48 +01:00
James Turner
db98c7440e Parallel sync of items.
When using built-in sync code, separate items into distinct slots.
Slots process items sequentially, but each slot works in parallel (using 
a single shared HTTP engine). This allows tiles to be synced in parallel
with airports/shared models data, greatly increasing responsiveness
to get tiles synced on initial launch.
2013-09-25 16:31:10 +01:00
James Turner
3783ae2234 Rename WaitingTile to WaitingSyncItem.
We already synchronise other things besides tiles, and will soon want different
behaviour based on the type of item we're syncing (so we can synchronize some items in parallel). Rename the struct to be clearer, and add an enum encoding the type of item for future use.
2013-09-23 18:20:16 +01:00
James Turner
3e8732b230 Libsvn / APR dependency is removed.
Remove any use of libsubversion / API in Simgear, and always
compile the built-in SVN client code.
2013-09-23 18:05:37 +01:00
James Turner
b39bca9458 Fix no-svn build (Win64 only at present) 2013-09-20 09:18:43 +01:00
James Turner
d263334030 Attempting to fix import/export linkage on Windows.
Hopefully this tells the Expat headers not to do any clever
declspec(import) or export stuff, which we don't want since we only
use the symbols within SimGearCore, and don't export them.
2013-09-19 22:07:41 +01:00
James Turner
b2cea62189 Even more forcing of include file names.
(It's a good thing Windows doesn't ship Expat, since apparently
every other platform does)
2013-09-19 21:58:23 +01:00
James Turner
d6b886c69b Change got lost in the move/rename, not good. 2013-09-19 21:47:40 +01:00
James Turner
38fb9ea41e Intermediate static libs have issues.
Switch back to including the bundled expat sources directly in the
targets, since transitive linking of static libs is very awkward.
Makes static build happy again (let's see what else breaks)
2013-09-19 21:42:28 +01:00
James Turner
a922aaa68e Move bundled Expat to new home.
Renamed bundled Expat headers to avoid any
possibility of accidental including system headers when
using bundled Expat, or vice-versa. Should help with SVN
crashes reported by Thomas.
2013-09-19 20:12:32 +01:00
James Turner
4f2e36ca46 rand(), not random(), for Windows. 2013-09-19 08:57:34 +01:00
James Turner
f367627cac Fix HTTP unit test.
Also fix a signed/unsigned warning I caused.
2013-09-18 22:55:11 +01:00
James Turner
7a7fcf10ad Fix missing include for non-Mac 2013-09-18 22:13:04 +01:00
James Turner
b9bd2734eb Persistent SVN update cache.
Make the already present cache of updated URLs persistent, with a
definable lifetime, currently 24 hours. This ensures terrasync-ed
resources are checked less often (max once per day) instead of each
FGFS launch, which should greatly cut down requests to the backend.
2013-09-18 21:40:35 +01:00
James Turner
9c7bd4f5d5 HTTP Client improvements
- max connections limit, and parallel connections to a single host where possible.
  (Will permit updating terrain and Models / Airports / data in parallel)
- add LGPL headers
- give HTTP::Client a private impl class, to keep header simple.
2013-09-18 12:17:38 +01:00
James Turner
362d47f91f Kill legacy PPM support in screen-dump 2013-09-18 12:17:29 +01:00
James Turner
a18792c397 Printf format sanitising.
Refactored version for next, use a new helper in
simgear::strutils.
2013-09-15 13:30:12 +01:00
James Turner
0e2ddb2f16 Bah, tested on the wrong compiler, so missed a file. 2013-09-14 18:05:53 +01:00
James Turner
bcfadd6657 Prefix GCC version constant.
From: Markus Wanner <markus@bluegap.ch>
2013-09-14 17:29:59 +01:00
Thomas Geymayer
021cf1a2b4 Remove temporary file and add to .gitignore 2013-09-07 14:28:46 +02:00
James Turner
acf86be516 Rename MD5 code.
Linux crashes with this code seem to relate
to multiple instances of these symbols. Prefix
the SimGear versions to prove/disprove this.
2013-09-04 08:45:06 +01:00
James Turner
dd93eb88f2 Ensure sglog() singleton is threadsafe.
Slightly simplistic, but definitiely correct, method
for ensuring singleton creation is threadsafe. Can
be optimised if lock contention is ever an issue.
2013-09-04 08:45:06 +01:00
James Turner
e06f9e35b3 Tweak for 64-bit Linux linkage. 2013-09-04 08:45:06 +01:00
James Turner
c0424aba7e SG part of fix for TerraSync models.
Ensure we search the TerraSync models tree before the FG_ROOT one,
since the TerraSync one contains newer files and updates to the base
one.
2013-08-29 22:13:43 +01:00
Thomas Geymayer
b468254d73 Fix #1163 2013-08-17 17:47:23 +02:00
Tom Paoletti
15e3e92ec2 Performance optimization: empty() instead of size()>0
empty() is guaranteed to be constant complexity for both vectors and lists, while size() has linear complexity for lists.
2013-08-16 16:48:35 +01:00
James Turner
ee403fd83a No need to deal with OSG versions pre 3.0 2013-07-29 22:53:31 +01:00
James Turner
05f266ac10 HTTP SVN fixes, cap max update-report depth. 2013-07-28 21:07:40 +01:00
James Turner
f52c0026dd Better reporting when an HTTP request fails. 2013-07-28 21:06:17 +01:00
Thomas Geymayer
a284107249 Canvas: ensure render order equals order in property tree.
Prevents also changing the render order if changing eg. the size
of a Canvas.
2013-07-26 23:31:46 +02:00
James Turner
a2e25e7940 Fix for #587, crash on break/continue.
Look for use of break/continue outside of a loop context,
and report a parser error instead of crashed. Thanks to
Philosopher for the patch.
2013-07-23 21:58:38 +01:00
James Turner
d45a0161cf Initialise token precedence rule.
Thanks to Philosopher for the catch.
2013-07-23 21:51:47 +01:00
James Turner
c7a5d15e7a Bug 585, assignment to multiple l-values.
Disambiguate multiple-values from function invocation. Will merge
to release branch after testing.
2013-07-23 09:45:22 +01:00
James Turner
3cb2241b1d Bug 737, crash parsing default args.
Reject function evaluation explicitly when building
default arguments.
2013-07-23 09:45:22 +01:00
Thomas Geymayer
b236821661 Canvas: Ensure all element types are initialized before first usage.
If setting properties on a group a check is performed if
this property exists on any possible child element, and
only if this is the case the property is stored in the
groups style.

Previously elements have been only initialized during
their first usage, leading to ignored styles if they
have been set on a parent element before instantiating
an instance of the actual element type.
2013-07-20 15:45:02 +02:00
Thomas Geymayer
fc75b0bd21 Canvas::Text: add line-height property. 2013-07-19 23:38:08 +02:00
Thomas Geymayer
a9fc84d568 Canvas: clip region rounding and catch negative size. 2013-07-19 23:37:04 +02:00
Thomas Geymayer
b15c6d887d Canvas: don't crash with invalid clip rect 2013-07-18 15:35:44 +02:00
Thomas Geymayer
c2f89978ff canvas::Element: Fix bounding box calculation of groups 2013-07-18 14:16:13 +02:00
James Turner
a004153223 Bump version on development trunk. 2013-07-17 20:29:22 +01:00
Thomas Geymayer
dde2bbcbe7 nasal::String: add ends_with method 2013-07-15 22:26:11 +02:00
Thomas Geymayer
7bd572f3ec Fix inverted checks... 2013-07-13 14:06:11 +02:00
Thomas Geymayer
486011dceb Fix for OSG without OSG_INIT_SINGLETON_PROXY 2013-07-13 11:37:24 +02:00
Thomas Geymayer
01c45633b7 Canvas::Group: Print warning message if using expired Group instead of crashing 2013-07-12 18:00:53 +02:00
Thomas Geymayer
5320d0ecaa Fix render order of canvas elements 2013-07-12 16:13:06 +02:00
Thomas Geymayer
757ba03918 CSSBorder fix: width/height swapped 2013-07-06 12:05:19 +02:00
Thomas Geymayer
948db69cc9 interpolate without values is used to abort interpolating -> no warning 2013-07-04 19:46:27 +02:00
Thomas Geymayer
92f363926e Replace auto_ptr to prevent possible undefined behavior 2013-07-04 16:09:04 +02:00
Thomas Geymayer
d0ff144753 Canvas: Fix removing text elements.
Removing text elements failed due to prefering removing the
style property with the same name. Add a flag to each style
indicating whether it can be inherited. The text property
is only applicable to text elements and can not be inherited.
Now groups can not have a text property, avoiding the name
clash with the text element.
2013-07-04 14:11:54 +02:00
Thomas Geymayer
943efbb3ed Canvas Event System: add missing currentTarget field. 2013-07-04 00:53:49 +02:00
Thomas Geymayer
6962de4b1f Canvas: fix property inheritance.
- Do not override style of children if they have own values set.
 - Retrieve style of parent if own style value has been removed.
2013-07-03 00:05:20 +02:00
Thomas Geymayer
6f7c0c23d1 Canvas: prevent bubbling of mouseenter and mouseleave. 2013-06-30 21:27:03 +02:00
Thomas Geymayer
14eccc70da Restore compatibility with stable OSG (3.0.1) 2013-06-30 21:25:21 +02:00
Thomas Geymayer
d4b48cec5d Canvas: clear mousedown/click if mouse leaves canvas. 2013-06-29 14:29:38 +02:00
Thomas Geymayer
2d23c5351f Canvas: Improve checks to mark events as handled. 2013-06-29 14:16:39 +02:00
Thomas Geymayer
b5dfaf170a Canvas: Ensure events are dispatched to canvas with no element hit. 2013-06-29 14:14:12 +02:00
Thomas Geymayer
c372591f36 Canvas: Ignore hidden element on event traversal. 2013-06-29 14:12:59 +02:00
Thomas Geymayer
36d1308aa6 Canvas: fix distance check for click events. 2013-06-29 14:11:19 +02:00
Thomas Geymayer
b0d6ed3844 Canvas: trigger missing events on mouseup.
On mouseup send mouseout/mouseleave to old element and
mouseenter/mouseover to new element. This fixes hover
actions after dragging.
2013-06-29 11:33:44 +02:00
Torsten Dreyer
5818366d92 Bump version number to 2.12.0 2013-06-28 12:09:30 +02:00
James Turner
e5bca74b9d std::string namespace fixes. 2013-06-27 10:14:08 +01:00
Thomas Geymayer
82dd1e4be9 Set binding after color array (required by OSG 3.1.8) 2013-06-27 00:38:44 +02:00
Alex Romosan
d0b77639c2 Ensure compatibility with OSG 3.1.8. 2013-06-26 23:54:09 +02:00
James Turner
6edb0f66d5 Fix crash when telnet or other protocols close.
http://code.google.com/p/flightgear-bugs/issues/detail?id=1144
2013-06-26 21:50:27 +01:00
James Turner
c458c13af7 XCode warning fixes. 2013-06-22 16:15:41 +01:00
James Turner
bb7875edd4 Fix a warning spotted by Emilian. 2013-06-22 16:15:30 +01:00
James Turner
6f54fb90eb Object names for STG groups / nodes. 2013-06-22 14:27:53 +01:00
James Turner
4d1e7c536f Fix preview exclusion of sliders. 2013-06-22 14:27:18 +01:00
James Turner
59cc200f92 Fix pick callbacks on scenery.
Ensure pick callbacks work over the whole pick group, otherwise
the visible render group is hit first, and resolves to the ground
pick callback instead.

This fixes the hangar doors at KSFO and Moffett.
2013-06-22 11:59:42 +01:00
Thomas Geymayer
c6093430ae Canvas: Fix creating/forwarding of mouseenter/mouseleave events. 2013-06-20 23:13:47 +02:00
James Turner
23172bcdd0 Windows SGPath::desktop() impl
Basic SHGetSpecialFolderPath wrapper, with dynamic
finding of the function since linking to shell32.dll
is painful. Obviously could be generalised to other
CSIDLs in the future.
2013-06-19 23:58:57 +01:00
James Turner
aea2dab0d7 Fix preprocessor boolean ops for MSVC 2013-06-19 09:04:15 +01:00
James Turner
4d26e144ab SVN client - prefix error constants.
Avoid Windows clashes on common names (NO_ERROR, etc)
2013-06-19 08:42:47 +01:00
Thomas Geymayer
c391fcd345 Canvas: fix updating placements after non matching placements 2013-06-18 23:07:20 +02:00
Thomas Geymayer
ece743342a canvas::Text: Make font resolution user configurable 2013-06-16 10:49:46 +02:00
Thomas Geymayer
02babbe340 canvas::Text: set font resolution to actual texel size. 2013-06-14 23:33:26 +02:00
Thomas Geymayer
e27b6d050c CanvasGroup: Do not try to remove already removed children. 2013-06-14 19:47:09 +02:00
Thomas Geymayer
86e2de8d10 Fix #1139
Always set TEST_LIBS as applications/tools also need to link
them, even if tests are disabled. Also allow disabling building
of sg_pkgutil.
2013-06-12 23:56:04 +02:00
James Turner
b4526f926d Use CoreServices, not Cocoa, for FSFindFolder 2013-06-12 22:11:43 +02:00
Thomas Geymayer
14f04878d1 CanvasGroup: allow derived classes to provide more/other child factories 2013-06-11 22:09:57 +02:00
Thomas Geymayer
830fb3b752 Use TEST_LIBS to include all libraries needed (on Mac) 2013-06-11 20:26:07 +02:00
Thomas Geymayer
9a336da359 Use global TEST_LIBS... 2013-06-11 19:41:29 +02:00
Thomas Geymayer
6cec49ce7e Missing library for Apple 2013-06-11 18:04:06 +02:00
Thomas Geymayer
6c28edea55 SGPath: new helpers and static members for home and desktop path 2013-06-10 21:37:00 +02:00
Thomas Geymayer
d7870d672e strutils: move unescape and simplify starts_with/ends_with 2013-06-10 21:36:13 +02:00
James Turner
5bd9228b4a Fix HTTP tests build. 2013-06-09 23:45:43 +01:00
James Turner
6392cf5a2f Fix Linux build of SVN code. 2013-06-09 23:14:25 +01:00
James Turner
6a2e2b704e Optionally, use internal code for SVN syncs. 2013-06-09 22:55:15 +01:00
James Turner
ecec803388 SVN read-only client code using our HTTP engine. 2013-06-09 19:19:03 +01:00
James Turner
848965e7f0 EasyXML: expose attributes easier. 2013-06-09 19:19:03 +01:00
James Turner
478af5f01e Base64 and hex helpers for strings 2013-06-09 19:19:03 +01:00
James Turner
14decb7387 simgear::Dir helpers
- remove all children of the dir
- check is the dir has children / is empty
2013-06-09 19:19:02 +01:00
James Turner
323d0d5ba0 HTTP engine tweaks for SVN support. 2013-06-09 19:19:02 +01:00
Thomas Geymayer
9bfa6ac1a1 Restructure Canvas/PropertyBasedElement
Nodes inside the osg scenegraph now hold a strong reference to
the according canvas element. Canvas elements in turn now only
hold a weak reference to the according node. This simplifies
for example the canvas::Group as it does not need to keep a
list of children on its own anymore. This is now stored inside
the scenegraph (as it was already before).

The restructuring will also allow to use a canvas::Group for
the canvas gui inside FlightGear and share for example the
handling of stacking based on z-index.
2013-06-08 11:28:49 +02:00
Thomas Geymayer
c8af817eeb CSSBorder: Do not return unitialized data. 2013-06-07 18:54:41 +02:00
Thomas Geymayer
d24d3ce487 Canvas: More helper functions and cleanup. 2013-06-07 16:45:34 +02:00
Thomas Geymayer
60a81dfbd8 CanvasGroup: Fix handling z-index while moving child to front. 2013-06-07 16:23:44 +02:00
Thomas Geymayer
8896a59dff Helper functions for SGRect and canvas::Element 2013-06-06 22:28:00 +02:00
Thomas Geymayer
7fe16d99be Canvas: clear event listeners on destroy
Removing all event listeneres on destroying a canvas
prevents circular references due to Nasal event listeners
keeping a reference to the canvas in their closure.

Also fix event handling with direct children of the root
group and add some more helpers to the Canvas.
2013-06-03 23:39:27 +02:00
Thomas Geymayer
c3af88dfc1 Canvas: set blend function for elements and prevent autoresize 2013-06-02 23:32:53 +02:00
Thomas Geymayer
5c8f0cc966 Make nasal::Ghost usable with weak_ptr 2013-06-02 21:20:47 +02:00
Thomas Geymayer
2a6c50c893 More helper methods for Canvas and PropertyBasedElement. 2013-06-02 21:19:37 +02:00
Thomas Geymayer
7a8d796ac1 Move BlendFunc parsing to separate file for easier reuse. 2013-06-02 20:41:03 +02:00
Thomas Geymayer
8b3b71bce3 Support for parsing basic CSS color keywords. 2013-05-31 19:18:36 +02:00
Thomas Geymayer
83da4e9248 Canvas Image: fix updating while showing nested canvas. 2013-05-31 19:17:30 +02:00
Thomas Geymayer
2c879095d9 Canvas: add chainable helpers to Path for adding segments. 2013-05-31 19:16:42 +02:00
Thomas Geymayer
13344fbb62 Canvas: separate CSSBorder parsing from image. 2013-05-31 19:14:39 +02:00
Thomas Geymayer
2fe9ad595f Canvas: bug fixing and add some helpers. 2013-05-31 19:07:55 +02:00
Stuart Buchanan
0f798289f0 Use square textures for trees, with shrunk UV coordinates. 2013-05-19 21:29:58 +01:00
Thomas Geymayer
01104cc1d3 Canvas: Add local_pos to mouse event and fix transformation of event positions with multi-level nested canvases. 2013-05-13 00:34:09 +02:00
Thomas Geymayer
d61b5827fd Canvas: proper forwarding of dirty and visible flags within nested canvases. 2013-05-13 00:26:13 +02:00
Stuart Buchanan
d18cc81531 Avoid UV bleeding issues on trees by using a strip of textures. 2013-05-10 20:03:48 +01:00
Thomas Geymayer
c96d7ae226 Use simulation time for spin and timed animations 2013-05-10 01:11:46 +02:00
Thomas Geymayer
9535aef59b Extend SGPickCallback to allow passing more information to callbacks 2013-05-09 21:31:27 +02:00
Thomas Geymayer
82f6fca06f Add (optional) uv coordinates to SGPickCallback 2013-05-09 12:40:59 +02:00
Thomas Geymayer
627b36a53b Prevent timer endless loops 2013-05-06 23:26:45 +02:00
Thomas Geymayer
2af78a3874 Allow adding a contition to locked-track animation 2013-05-02 00:47:51 +02:00
Stuart Buchanan
7c7109edf4 Reduce the height of tree UV coordinates to work around mipmap issues. 2013-05-01 22:01:45 +01:00
Thomas Geymayer
0fa23b83e6 Extend locked-track animation to support a slave element.
This allows tracking elements while at the same time changing the
rotation of both animated elements to fill the available distance
to the target element. This allows for example animating gear
scissors or other connected objects like gear doors and their
actuators/arms (possibly connected to the gear strut).
2013-04-28 23:46:25 +02:00
Thomas Geymayer
e53c71543a Move and rework pow to math/SGMisc 2013-04-28 15:43:02 +02:00
Thomas Geymayer
fa64f7b6aa Work around MSVC linker bug with classes derived from i/ofstream 2013-04-27 23:44:32 +02:00
Thomas Geymayer
eaac3a64f8 Fix linker error for test 2013-04-26 23:48:25 +02:00
Thomas Geymayer
4eabc45dfc Fix animation tests for low precision floating point 2013-04-26 00:19:00 +02:00
Thomas Geymayer
5c5e2a0f2f Remove debug output (and shorten test names) 2013-04-26 00:13:35 +02:00
Thomas Geymayer
559a5d146a Add trackTo (locked-track) animation 2013-04-25 23:23:16 +02:00
Stuart Buchanan
df46c58cb8 Reduce the texture coords for trees.
Support winter, summer and snow textures on single texture sheet.
2013-04-20 21:05:55 +01:00
Thomas Geymayer
1f43132363 Canvas: rework style setter system.
Style setters are now only setup once for each element type and
not for each element instance as before. A static map holds the
setters for each element type. Also an animation type is stored
which will later allow to animate properties of Canvas elements
without specifying and animation type.
2013-04-20 00:22:37 +02:00
Thomas Geymayer
17369a20de CanvaImage: Enable texture repeat for images 2013-04-15 21:09:41 +02:00
Thomas Geymayer
c83dae4c70 Comparison operators for SGSharedPtr 2013-04-13 16:21:04 +02:00
James Turner
c2f44a51c4 Restructure to avoid ordering issues.
Create a pick group for each object, instead of a single one. This
can be optimised in the future but this structure is backwards-
compatible with existing usage, which is more important.
2013-04-13 14:40:55 +01:00
Thomas Geymayer
3da44d1215 Nasal: add naVec_removelast. (Thanks to Philosopher) 2013-04-12 12:32:03 +02:00
James Turner
f6709e357f Support for proxy pick objects.
Pick, knob and slider animations support additional object-names,
which are always invisible and untransformed (in the case of knobs/
sliders). These objects act to extend the hover/pick area, making small
switches and knobs more usable. (Especially for users with smaller
screens / windows).
2013-04-05 17:12:58 +01:00
James Turner
45d5511fd7 Remove stray debug. 2013-04-02 18:10:09 +01:00
Thomas Geymayer
198c5d23fd nasal::Ghost tweaking to allow using with osg::oberser_ptr. 2013-04-01 13:35:41 +02:00
Thomas Geymayer
c41caeaf64 Ensure every scenery model has own SGModelData.
This makes Nasal unload hooks of scenery objects working again.
Previously the same SGModelData instance was used for all objects
which never got destroyed and therefore was not able to call any
unload callback.
2013-04-01 13:22:28 +02:00
Thomas Geymayer
b6c542b0e7 nasal::Ghost tweaking and to_nasal_helper for SGGeod. 2013-03-23 12:47:06 +01:00
Thomas Geymayer
2d62275a08 Fix uninitialized member.
Thanks to Chris for the catch.
2013-03-20 23:44:26 +01:00
Thomas Geymayer
971ea81861 cppbind: add from_nasal_helper to convert Nasal ghosts to C++ shared pointer 2013-03-19 18:36:55 +01:00
Thomas Geymayer
9e9cc7859c Interpolation system tweaking and add helpers to SGPropertyNode to interpolate its value 2013-03-17 23:48:01 +01:00
Thomas Geymayer
8898f5fe52 Tweak interpolator and allow passing list of interpolation steps 2013-03-16 16:36:20 +01:00
Thomas Geymayer
40be69ae8e Remove unecessary dependency from libSimGearCore on libSimGearScene. 2013-03-16 12:19:23 +01:00
Thomas Geymayer
17eec81071 Fix headless build 2013-03-16 10:35:30 +01:00
Thomas Geymayer
c9bbbd18ec New interpolation/animation system.
Inspired by jQuery.animate() properties can be interpolated using
different easing functions and specifying an animation duration.
Additionally animations can be chained to get table-based
animations like with the current SGInterpolator, or also create
looped animations or other more complicated curves.

Currently this system is not used yet, but it is intended to
replace SGInterpolator and allow more advanced animations of
eg. also colors, for example, for the canvas.
2013-03-15 23:37:17 +01:00
Thomas Geymayer
e08eda18d5 CMake: Create list of libs for testing only once 2013-03-15 23:21:38 +01:00
James Turner
d2c03a6651 Fix pick-callbacks with no action.
Ensure pick animations work only to show a tooltip. Thanks to Emilian
for noticing I broke this
2013-03-12 21:14:31 +00:00
Thomas Geymayer
0a1e920659 Ensure canvas is updated before displaying image containing a canvas 2013-03-11 21:22:43 +01:00
James Turner
cd58df820e Standardise location (in XML) of hovered elements.
Hovered bindings are always direction children of the pick animation, since having different hovered behaviours per-action makes no sense.
2013-03-11 18:07:27 +00:00
James Turner
1a5467aec8 Collect properties from expression/condition trees.
Initial ground-work to support efficient updating of condition/expression results; allow collecting all the dependent property values from the hierarchy, so they can be observed. Also add a very small test-case for this.
2013-03-11 16:53:52 +00:00
Thomas Geymayer
0dcb64dca3 slider - fix shifted check while dragging 2013-03-10 23:10:26 +01:00
James Turner
b703102d9b Part of fixing bug 1055.
Add machinery to convert hateful legacy Windows encodings to UTF-8.
2013-03-10 13:38:29 +00:00
James Turner
3c2ef75b50 Tweaks for pick callback cursors. 2013-03-10 12:03:09 +00:00
James Turner
4fa530354d Knob/slider - clean up internals.
Add support for a global sensitivity parameter to scale drag response.
2013-03-08 16:31:02 +00:00
James Turner
eeeb30dc3f Work on knob/slider animations.
Rename some values, and support mouse-dragging.
2013-03-07 18:40:37 +00:00
PlutoniumHeart
870fc2e53e Replacing strdup with _strdup in MSVC 2013-03-06 23:58:38 -07:00
Thomas Geymayer
79ae0da927 Fix some warnings 2013-03-07 00:17:45 +01:00
Thomas Geymayer
e179bda57a add static_pointer_cast for SGSharedPtr 2013-03-06 23:17:02 +01:00
Thomas Geymayer
8e0c15e32e cppbind: allow adding free functions accepting nasal::CallContext and do some cleanup 2013-03-06 17:00:38 +01:00
Thomas Geymayer
ff844f6760 May MSVC likes this more... 2013-03-05 18:12:17 +01:00
Thomas Geymayer
bc96ac85f4 cppbind: Add some methods to nasal::CallContext 2013-03-05 17:27:49 +01:00
James Turner
33b328e3d5 Fix priority of pick animations. 2013-03-05 08:39:44 +00:00
James Turner
dcda8d1c7a Add a pick priority lower than the default. 2013-03-04 23:33:21 +00:00
James Turner
5fd2bd225f Support motion-tracking in pick callbacks. 2013-03-04 23:11:40 +00:00
Thomas Geymayer
2db8859076 cppbind: Tweak from_nasal error reporting 2013-03-04 23:15:13 +01:00
Thomas Geymayer
6eff167a28 Silence MSVC 2013-03-04 18:42:32 +01:00
Thomas Geymayer
9fecb69b84 cppbind: tweaking from_nasal/to_nasal. 2013-03-04 16:26:28 +01:00
Thomas Geymayer
0539aa38e5 cppbind: faster from_nasal for bool and fix VS warning. 2013-03-04 00:30:30 +01:00
James Turner
fe9caad391 Max catalog age configurable per-catalog.
Allows for short-validity catalogs for development use.
2013-03-03 23:03:09 +00:00
Thomas Geymayer
ceae2928aa cppbind: refactor to_nasal for better name lookup.
Using template parameter dependent name lookup it is
now possible to create to_nasal_helper overloads
for custom types and having them used for automatic
type conversion with binding methods/member on
nasal::Ghost objects.
2013-03-03 23:42:40 +01:00
Christian Schmitt
0e6934abe5 Add a missing define for minizip, needed on some installations of zlib 2013-03-03 23:35:43 +01:00
Thomas Geymayer
a2e7c92f99 cppbind: Allow getter without from_nasal defined and setter without to_nasal. 2013-03-03 20:55:57 +01:00
Thomas Geymayer
643a8717b4 Make old gcc happy 2013-03-03 20:16:21 +01:00
Thomas Geymayer
f21127fd4a cppbind: rework to allow binding nearly everything.
It is now possible to register all types of member function
and free functions as methods for nasal::Ghost objects.
The return value and arguments are converte automatically
to the required types.

Also usage is simplified by removing replacing the old
method and method_func with a single method function
which only needs a name for the method and something
callable.
2013-03-03 19:28:39 +01:00
Peter Sadrozinski
0bc8005100 Add new api to retreive a list of SGBuckets within a bounding rectangle 2013-03-03 19:21:05 +01:00
James Turner
67efc1f68f Look for MSVC 3rdparty deps differently.
Check for the 3rdparty dir in the parent of build dir, not the parent of the source dir. For the recommended build layout, this is the same location, but for super-builds using fgmeta it's not (and the source tree should not be touched).

If this causes anyone issues, please let me know, since it's possible more flexibility is needed to set the path explicitly.
2013-03-03 16:06:51 +00:00
James Turner
dfebf60e68 Simplify CMakeLists now package code is enabled. 2013-03-03 16:01:06 +00:00
James Turner
26e7d134ce Package management tweaks. 2013-03-03 15:03:25 +00:00
Thomas Geymayer
c414242f13 sg_netChat: let getTerminator return const char* for compatiblity with FlightGear 2013-03-03 01:19:35 +01:00
Thomas Geymayer
33e60725b1 sg_netChat: Use std::string to prevent crash with old strdup.
Thanks to Godspeed for noticing this code crashing with MSVC.
Using std::string should fix this.
2013-03-03 01:12:01 +01:00
James Turner
f21b0985cc pkgutil can show detailed info about a package. 2013-03-02 17:02:08 +00:00
James Turner
71e9548c20 Lots more work on package support.
Delegate is hooked up in the demo util, and Install forwards control to the delegate too.
2013-03-02 16:30:39 +00:00
James Turner
fc64abea5c LGPL license on package files. 2013-03-02 15:15:52 +00:00
James Turner
413e89c955 Package dependencies. 2013-03-02 15:14:09 +00:00
James Turner
439041c2f4 Enable package code by default. 2013-03-02 14:43:19 +00:00
Thomas Geymayer
530de4d809 cppbind: Prepare for improved bindings.
- Improved Nasal/C++ bindings will follow. For now just test if
   all compilers are happy with intended approach.
 - Add to_nasal overload for std::map<std::string, T>.
2013-03-02 00:13:48 +01:00
Thomas Geymayer
5e45bdeeda Add optional user_data to Nasal C functions.
A user_data pointer and another pointer to an optional
deleter function is stored in unused parts of the naPtr
union. The previous behavior of extension functions does
not change. Only one additional boolean comparison is
required upon each function call to check whether user
data is available.
2013-03-01 12:22:51 +01:00
Thomas Geymayer
081eba903f Visual Studio fix 2013-03-01 00:33:47 +01:00
Thomas Geymayer
0def045611 cppbind: Fix SGPath from_nasal_helper 2013-02-28 23:46:55 +01:00
Thomas Geymayer
e20cc6a90f Nasal cppbind: support more member function types 2013-02-28 23:43:42 +01:00
Thomas Geymayer
7dd83dd2d1 Fix boolean conversion 2013-02-28 22:23:30 +01:00
Thomas Geymayer
1240807b9f ODGauge/Canvas: add option "additive-blend" to use additive alpha blending 2013-02-28 22:04:16 +01:00
James Turner
ad7d6624de Fix some signed/unsigned comparisoms. 2013-02-28 19:45:19 +00:00
James Turner
06b089ba13 Updated package-management code.
Use mini-unzip code to extract, since we don't need all the features of libArchive, and the deployment issues become simpler.
2013-02-28 18:31:28 +00:00
Thomas Geymayer
e1302bcf17 CanvasImage: Fix border abs/rel calculations; add slice-width property 2013-02-28 01:09:27 +01:00
Thomas Geymayer
cfdc7db79a sgstream_test needs linking with zlib 2013-02-26 12:53:56 +01:00
Thomas Geymayer
50de873453 Canvas: ensure z-index is updated on adding new child. 2013-02-26 12:49:04 +01:00
Christian Schmitt
a4ae7b2059 sgstream: detect two directly following CR/LF EOL correctly in skipeol,
supply a testcase for this.
2013-02-26 11:56:14 +01:00
Mathias Froehlich
d90abdcb44 scenery: Make static scenery static. 2013-02-25 06:55:35 +01:00
Mathias Froehlich
4a31045fd9 spt: Make the range multiplier configurable. 2013-02-25 06:42:55 +01:00
Mathias Froehlich
249155987d spt: Normalize the bucket box meta filename lengths. 2013-02-25 06:42:55 +01:00
Mathias Froehlich
2ae7fc244b spt: Expose the paging levels to osg options.
The levels at which page nodes are built is exposed by an
osg database pager option SimGear::SPT_PAGE_LEVELS which could
contain a blank separated list of levels where paging occurs.
Also the database pagers paths are searched for static lower
level of detail tiles in case they are available.
2013-02-25 06:42:55 +01:00
Mathias Froehlich
fafa70c6bc spt: Use vbos for geometry. 2013-02-25 06:42:40 +01:00
Mathias Froehlich
49162a7a64 stg: Defer loading models into a paged lod node.
This defers loading the models into a paged lod node
that is evaluated at a later point in time. That should
help for memory problems with higher visibilities.
2013-02-25 06:42:40 +01:00
Thomas Geymayer
328e6b3e28 Update bounding box if calling update() on canvas elements 2013-02-24 20:44:02 +01:00
Thomas Geymayer
5441db9b18 Support image slice/scale-9 for CanvasImage. 2013-02-24 17:57:19 +01:00
Thomas Geymayer
06bfa4f1a1 Fix package management compiling with linux 2013-02-21 14:03:08 +01:00
James Turner
a530712491 Initial work on package management.
Basic library infrastructure, catalog download/refresh, and package install,
uninstall and update. Disabled at cmake time by default, and not yet hooked
into FlightGear.
2013-02-20 16:17:22 +00:00
James Turner
3c8cfe9b76 Fix MSVC compilation, thanks to Fred. 2013-02-18 13:56:41 +00:00
Eric van den Berg
57b4ce96e4 added physical constant cp 2013-02-18 08:58:52 +00:00
Hooray
63c7d64143 fix mingw 2013-02-18 08:58:52 +00:00
Thomas Geymayer
1784327c47 CanvasImage: Use image/canvas size rather than texture size 2013-02-15 12:32:31 +01:00
Thomas Geymayer
a63ec83d5f CanvasImage: Use normalized coordinates by default.
Using absolute sizes by default lead to problems,
as while creating the image the size of the later
texture is not available.
2013-02-14 17:32:19 +01:00
Thomas Geymayer
d661516b02 Expose whether CanvasElement is visible 2013-02-13 12:09:15 +01:00
James Turner
ba020245f9 Fix jpeg-httpd compilation. 2013-02-11 12:51:20 +00:00
James Turner
79fefc1ff9 Support arbitrary parameters to bindings.
Use this to supply window X/Y to hover bindings.
2013-02-10 15:07:55 +00:00
James Turner
eac169b6da Grrrr. 2013-02-09 15:45:36 +00:00
James Turner
a60d293759 Tweak APi for GCC happiness. 2013-02-09 14:11:48 +00:00
James Turner
b0063f8db6 Command-manager supports functors.
Will be used to support native Nasal commands, which was tricky with pure function pointers before. Can also be used to avoid some trampoline functions in the code, but this can happen gradually.
2013-02-09 12:40:44 +00:00
Thomas Geymayer
632abdd87b Fix CanvasText horizontal alignment.
Don't reduce width of bounding box below character width
even if outline of rendered character is smaller. This
prevents trailing spaces from being ignored and also
fixes continuously chaging alignment (especially with
monospace fonts).
2013-02-09 12:17:45 +01:00
James Turner
d9b01ca5de Foundations of hover support in pick-callbacks. 2013-02-09 11:11:53 +00:00
James Turner
adb7db9229 Ensure individual log-level setting works. 2013-02-08 20:17:27 +00:00
James Turner
e4f7aec965 Linux needs explicit <algorithm> include. 2013-02-08 20:07:11 +00:00
James Turner
768a8c1062 Give terrasync a buffered log. 2013-02-08 19:37:47 +00:00
James Turner
318c5000ce Logging tweaks.
More pieces to support logging to buffers in the GUI.
2013-02-08 19:37:29 +00:00
James Turner
70f334e1a3 Fix GCC warnings about signed/unsigned compare. 2013-02-08 12:58:02 +00:00
James Turner
279b53a705 Move BufferedLogCallback to its own class.
Reduces includes in logstream.hxx, which is included by everyone and hence needs to be lightweight.
2013-02-08 12:58:02 +00:00
James Turner
a72a3ce5f3 Fix stderr log filtering.
Thanks to Stuart for pointing this out!
2013-02-08 12:58:02 +00:00
Thomas Geymayer
979aea5212 Add SGMisc::seek helper 2013-02-08 12:12:15 +01:00
James Turner
34d3c63384 Fix bucketBoxTest linkage. 2013-02-07 17:53:34 +00:00
James Turner
cf4af2692b Fix property tests static linkage. 2013-02-07 17:51:42 +00:00
James Turner
d335ca5a8d A couple more tests fixes. 2013-02-07 17:48:58 +00:00
James Turner
9cac2c1457 Fix math test linkage. 2013-02-07 17:42:00 +00:00
James Turner
523b992b4c Windows logging tweaks.
Restore the freopen calls, though I am unsure exactly what they achieve.
2013-02-07 17:23:31 +00:00
Christian Schmitt
02be490466 Missing headers 2013-02-07 18:20:57 +01:00
James Turner
45ae3978f6 Fix handling of ZSTREAM_END result from zlib.
Avoid getting stuck when ZSTREAM_END occurs with no more bytes written out.
2013-02-07 13:01:56 +01:00
James Turner
f983194d7e Buffered log-callback.
Allow a particular set of log messages to be retained indefinitely, for (presumably) later display somehow, such as in the GUI.
2013-02-07 13:01:56 +01:00
James Turner
ab8373b989 Rewrite logging code.
Support multiple backends, and make it thread-safe. Use an internal thread to push log entries to backends, to avoid blocking the submitting thread for any time.
2013-02-07 13:01:56 +01:00
Thomas Geymayer
4bef2c48eb Remove debug output 2013-02-07 10:10:38 +01:00
Thomas Geymayer
21e2a769eb Fix PropertyObject bug and interface improvements.
- Fix overwriting of parent node if PropertyObject::node() is
   called for a non-existing node.
 - Prevent implicit conversion from const char* and
   SGPropertyNode*
2013-02-07 01:25:55 +01:00
Stuart Buchanan
fd8369142a Building performance improvements.
Use sensible LoD range of 10km and gradual fade-out to 20km rather
than fixed 20km+ LoD range.  Also make deeper quad-tree to make
culling easier.
2013-02-06 21:53:57 +00:00
Thomas Geymayer
5dea221ad5 Expose reloading Canvas placements 2013-02-06 01:19:24 +01:00
James Turner
235c29913a Make the knob mouse-wheel direction configurable.
Add a global setting to control the mouse-wheel direction of the knob; a future commit will expose this in the FG GUI.
2013-02-03 17:21:50 +00:00
James Turner
0870407f65 Add 'release' bindings to knob animation.
Currently this fires on button release, but also after any mouse-wheel movement (since they are sent as buttons). This feels slightly wrong, but can't decide how a spring-loaded know would work, when interacting via the mouse-wheel. Comments welcome.
2013-02-03 17:11:05 +00:00
James Turner
e8156c6bd9 Knob animation: shifted mode binding.
As part of this, pass the osgGA GUIEventAdapter down into the pick callbacks, which seems reasonable to me. If anyone can foresee issues with this, please let me know and I can adjust the API.
2013-02-03 16:33:30 +00:00
Thomas Geymayer
e7f9486aa1 Nasal String wrapper and allow adding methods to string objects.
- Add nasal::String for wrapping Nasal string data and accessing
   string methods (which eg. could be exposed to Nasal)
 - Allow adding functions from C/C++ which are callable on
   Nasal strings.
2013-01-31 19:07:33 +01:00
James Turner
53b9fd2110 Support preview mode in the model loader.
This makes fgviewer / fgfs --viewer mode useful with models containing Rembrandt light volumes.
2013-01-31 16:46:02 +00:00
James Turner
4d4e474464 Initial prototype of knob animation. 2013-01-31 00:15:09 +00:00
James Turner
a58b41e7d2 Pick animation moved to its own file.
Preparatory work for adding more pick animations which will share some
internals with this code.
2013-01-30 21:51:41 +00:00
Mathias Froehlich
4db05e97c5 spt: The bucket size case is already handled above. 2013-01-29 07:01:58 +01:00
Mathias Froehlich
3a668232c8 spt: Introduce a third layer of paged nodes. 2013-01-29 07:01:50 +01:00
Eric van den Berg
d2622a5d86 added some physical constants to constants.h
goes with fgfs merge request #38
2013-01-28 16:56:07 +00:00
James Turner
d11db8a67a Prepare for optionally embedded SimGear. 2013-01-26 15:31:17 +01:00
James Turner
98b6102e56 State-machine correctness tweaks. 2013-01-25 12:05:16 +01:00
James Turner
0db8613a21 StateMachine tweaks. 2013-01-25 09:29:31 +01:00
James Turner
94a6cb2cff Tolerate lack of C99 in MSVC. 2013-01-23 16:01:46 +01:00
James Turner
5b92575ed3 Fix state-machine test linkage with static libs.
Thanks to Thomas for the catch.
2013-01-22 20:56:57 +01:00
Thomas Geymayer
47b02c0480 Missing include 2013-01-22 19:20:41 +01:00
James Turner
691be54ca2 Keep Linux happy. 2013-01-22 18:53:51 +01:00
James Turner
22ea8ebe25 State-machine structure, initial work. 2013-01-22 18:08:44 +01:00
James Turner
bd71635c49 Add left/right Nasal library functions. 2013-01-22 18:06:50 +01:00
James Turner
830bc3eac3 More native math functions in Nasal. 2013-01-22 18:06:50 +01:00
Mathias Froehlich
2d72bf4308 spt: Put the sea level textured tiles also under a transform node. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
f4e694afa7 spt: Consistently return references in the loader. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
55610bae70 spt: Use SGBox to build up the bounding spheres for the tiles. 2013-01-20 15:33:25 +01:00
Mathias Froehlich
08351e0df2 bucket: Make no bucket cross the 0 and 180 deg longitude border.
Change the bucket tiling at the poles not to cross the
0deg longitude and not to cross the 180deg longitude line.
This lets some special cases disappear for the buckets as well
as for the hierarchical level of detail spt loader.
Also change the last degree tiles from spanning 360 degrees to
12 degrees. Before we had 8 nested rings which neither helps
scenery paging nor culling. With that change the chunks are
about the same order of size than any other tile. Also the
tile shapes are much more friendly for culling and paging.
Also in presence of the current scenery, this polar tiles contain
as of today just totally broken geometry which tells me that
also the scenery generation process did not like these rings
for the tile shapes.

The impact on the current scenery is low. With this change the
polar regions do no longer load the tiles that are available
in the current scenery builds. The last degree tiles are broken
in this scenery version anyway. The next degree that is changed
will really loose some tiles that were sensible as of today.
All these areas are replaced with sea tiles by the old paging
structure. So, you will have at least ground below you when you
fly there. A hopefully comming soon scenery rebuild will
use this new tiling structure and thus will be the first one
providing closed polar caps.
2013-01-20 15:33:25 +01:00
372 changed files with 36174 additions and 7960 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.*
*~
Makefile
*.o
lib*.a

4
3rdparty/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,4 @@
if (NOT SYSTEM_EXPAT)
add_subdirectory(expat)
endif()

33
3rdparty/expat/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,33 @@
configure_file (
"${PROJECT_SOURCE_DIR}/3rdparty/expat/expat_config_cmake.in"
"${PROJECT_BINARY_DIR}/3rdparty/expat/expat_config.h"
)
set(expat_sources
asciitab.h
hashtable.h
iasciitab.h
latin1tab.h
nametab.h
utf8tab.h
xmldef.h
xmlparse.h
xmlrole.h
xmltok.h
xmltok_impl.h
hashtable.c
xmlparse.c
xmlrole.c
xmltok.c
internal.h
ascii.h
sg_expat.h
sg_expat_external.h
)
foreach(s ${expat_sources})
set_property(GLOBAL
APPEND PROPERTY LOCAL_EXPAT_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/${s}")
endforeach()

View File

@@ -15,7 +15,7 @@
#endif
#include <stdlib.h>
#include "expat_external.h"
#include "sg_expat_external.h"
#ifdef __cplusplus
extern "C" {

View File

@@ -23,7 +23,7 @@
#endif /* ndef COMPILED_FROM_DSP */
#include "ascii.h"
#include "expat.h"
#include "sg_expat.h"
#ifdef XML_UNICODE
#define XML_ENCODE_MAX XML_UTF16_ENCODE_MAX

View File

@@ -18,7 +18,7 @@
#endif
#endif /* ndef COMPILED_FROM_DSP */
#include "expat_external.h"
#include "sg_expat_external.h"
#include "internal.h"
#include "xmlrole.h"
#include "ascii.h"

View File

@@ -18,7 +18,7 @@
#endif
#endif /* ndef COMPILED_FROM_DSP */
#include "expat_external.h"
#include "sg_expat_external.h"
#include "internal.h"
#include "xmltok.h"
#include "nametab.h"

View File

@@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 2.6.4)
include (CheckFunctionExists)
include (CheckIncludeFile)
include (CheckCXXSourceCompiles)
include (CheckCXXCompilerFlag)
project(SimGear)
@@ -10,6 +10,8 @@ project(SimGear)
file(READ version versionFile)
string(STRIP ${versionFile} SIMGEAR_VERSION)
set(FIND_LIBRARY_USE_LIB64_PATHS ON)
# use simgear version also as the SO version (if building SOs)
SET(SIMGEAR_SOVERSION ${SIMGEAR_VERSION})
@@ -85,13 +87,6 @@ if(NOT "${CMAKE_LIBRARY_ARCHITECTURE}" STREQUAL "")
message(STATUS "additional library directories: ${ADDITIONAL_LIBRARY_PATHS}")
endif()
if(NOT MSVC)
# TBD: are these really necessary? Aren't they considered by cmake automatically?
list(APPEND ADDITIONAL_LIBRARY_PATHS
/opt/local
/usr/local
/usr)
endif()
#####################################################################################
if (NOT MSVC)
@@ -105,14 +100,13 @@ set(SYSTEM_EXPAT OFF)
endif()
option(SIMGEAR_HEADLESS "Set to ON to build SimGear without GUI/graphics support" OFF)
option(JPEG_FACTORY "Enable JPEG-factory support" OFF)
option(ENABLE_LIBSVN "Set to ON to build SimGear with libsvnclient support" ON)
option(ENABLE_RTI "Set to ON to build SimGear with RTI support" OFF)
option(ENABLE_TESTS "Set to OFF to disable building SimGear's test applications" ON)
option(ENABLE_SOUND "Set to OFF to disable building SimGear's sound support" ON)
option(ENABLE_PKGUTIL "Set to ON to build the sg_pkgutil application (default)" ON)
if (MSVC)
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} PATH)
GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_BINARY_DIR} PATH)
if (CMAKE_CL_64)
SET(TEST_3RDPARTY_DIR "${PARENT_DIR}/3rdparty.x64")
else (CMAKE_CL_64)
@@ -165,6 +159,13 @@ if (MSVC AND MSVC_3RDPARTY_ROOT)
set (OPENAL_LIBRARY_DIR ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib)
endif (MSVC AND MSVC_3RDPARTY_ROOT)
if(APPLE)
find_library(COCOA_LIBRARY Cocoa)
endif()
# Somehow this only works if included before searching for Boost...
include(BoostTestTargets)
find_package(Boost REQUIRED)
set (BOOST_CXX_FLAGS "-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_BIMAP_DISABLE_SERIALIZATION")
@@ -180,34 +181,9 @@ else()
message(STATUS "Sound support: ENABLED")
endif(ENABLE_SOUND)
find_package(OpenSceneGraph 3.0.0 REQUIRED osgText osgSim osgDB osgParticle osgGA osgUtil)
find_package(OpenSceneGraph 3.2.0 REQUIRED osgText osgSim osgDB osgParticle osgGA osgUtil)
endif(SIMGEAR_HEADLESS)
if(JPEG_FACTORY)
message(STATUS "JPEG-factory: ENABLED")
find_package(JPEG REQUIRED)
include_directories(${JPEG_INCLUDE_DIR})
else()
message(STATUS "JPEG-factory: DISABLED")
endif(JPEG_FACTORY)
if(ENABLE_LIBSVN)
find_package(SvnClient)
if(LIBSVN_FOUND)
message(STATUS "Subversion client support: ENABLED")
set(HAVE_SVN_CLIENT_H 1)
set(HAVE_LIBSVN_CLIENT_1 1)
else()
# Oops. ENABLE_LIBSVN is ON, but svn is still missing.
# Provide clearly visible warning/hint, so builders know what else they should install (or disable).
message(WARNING "Failed to enable subversion client support. Unable to find required subversion client library. Some features may not be available (scenery download).")
message(WARNING "Install 'libsvn' library/DLL (libsvn-devel/libsvnclient/...). Otherwise disable subversion support (set 'ENABLE_LIBSVN' to 'OFF').")
endif(LIBSVN_FOUND)
else()
message(STATUS "Subversion client support: DISABLED")
endif(ENABLE_LIBSVN)
find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED)
@@ -215,12 +191,19 @@ if (SYSTEM_EXPAT)
message(STATUS "Requested to use system Expat library, forcing SIMGEAR_SHARED to true")
set(SIMGEAR_SHARED ON)
find_package(EXPAT REQUIRED)
include_directories(${EXPAT_INCLUDE_DIRS})
else()
message(STATUS "Using built-in expat code")
add_definitions(-DHAVE_EXPAT_CONFIG_H)
# XML_STATIC is important to avoid sg_expat_external.h
# declaring symbols as declspec(import)
add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC)
set(EXPAT_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/3rdparty/expat
${PROJECT_BINARY_DIR}/3rdparty/expat)
endif(SYSTEM_EXPAT)
include_directories(${EXPAT_INCLUDE_DIRS})
check_include_file(inttypes.h HAVE_INTTYPES_H)
check_include_file(sys/time.h HAVE_SYS_TIME_H)
check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
@@ -269,6 +252,22 @@ if(HAVE_CLOCK_GETTIME)
endif(HAVE_RT)
endif(HAVE_CLOCK_GETTIME)
set(DL_LIBRARY "")
check_cxx_source_compiles(
"#include <dlfcn.h>
int main(void) {
return 0;
}
"
HAVE_DLFCN_H)
if(HAVE_DLFCN_H)
check_library_exists(dl dlerror "" HAVE_DL)
if(HAVE_DL)
set(DL_LIBRARY "dl")
endif()
endif()
SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually 'd' on windows")
SET(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
SET(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
@@ -302,6 +301,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(WARNING_FLAGS_C "-Wall")
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# boost goes haywire wrt static asserts
check_cxx_compiler_flag(-Wno-unused-local-typedefs HAS_NOWARN_UNUSED_TYPEDEFS)
if(HAS_NOWARN_UNUSED_TYPEDEFS)
set(WARNING_FLAGS_CXX " ${WARNING_FLAGS_CXX} -Wno-unused-local-typedefs")
endif()
endif()
if(WIN32)
if(MINGW)
@@ -314,7 +321,10 @@ if(WIN32)
# SET(WARNING_FLAGS "${WARNING_FLAGS} /wd${warning}")
# endforeach(warning)
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /wd4996 /wd4250")
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /wd4996 /wd4250 -Dstrdup=_strdup")
if (${MSVC_VERSION} GREATER 1599)
set( MSVC_LD_FLAGS "/FORCE:MULTIPLE" )
endif (${MSVC_VERSION} GREATER 1599)
endif(MSVC)
# assumed on Windows
@@ -326,15 +336,19 @@ endif(WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
include_directories(${PROJECT_BINARY_DIR}/simgear)
include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
# use BEFORE to ensure local directories are used first,
# ahead of system-installed libs
include_directories(BEFORE ${PROJECT_SOURCE_DIR})
include_directories(BEFORE ${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include)
include_directories(BEFORE ${PROJECT_BINARY_DIR}/simgear)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}
${OPENAL_INCLUDE_DIR} )
${Boost_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIR}
${OPENAL_INCLUDE_DIR}
)
add_definitions(-DHAVE_CONFIG_H)
@@ -345,22 +359,33 @@ configure_file (
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
)
configure_file (
"${PROJECT_SOURCE_DIR}/simgear/xml/expat_config_cmake.in"
"${PROJECT_BINARY_DIR}/simgear/xml/expat_config.h"
)
if(ENABLE_TESTS)
# enable CTest / make test target
message(STATUS "Tests: ENABLED")
include (Dart)
enable_testing()
enable_testing()
else()
message(STATUS "Tests: DISABLED")
endif(ENABLE_TESTS)
# always set TEST_LIBS as it is also used by other tools/applications
set(TEST_LIBS_INTERNAL_CORE
${CMAKE_THREAD_LIBS_INIT}
${ZLIB_LIBRARY}
${WINSOCK_LIBRARY}
${RT_LIBRARY}
${DL_LIBRARY}
${COCOA_LIBRARY})
set(TEST_LIBS SimGearCore ${TEST_LIBS_INTERNAL_CORE})
if(NOT SIMGEAR_HEADLESS)
set(TEST_LIBS SimGearScene ${OPENGL_LIBRARIES} ${TEST_LIBS})
endif()
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
add_subdirectory(3rdparty)
add_subdirectory(simgear)
#-----------------------------------------------------------------------------
@@ -372,3 +397,5 @@ CONFIGURE_FILE(
IMMEDIATE @ONLY)
ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")

View File

@@ -0,0 +1,256 @@
# - Add tests using boost::test
#
# Add this line to your test files in place of including a basic boost test header:
# #include <BoostTestTargetConfig.h>
#
# If you cannot do that and must use the included form for a given test,
# include the line
# // OVERRIDE_BOOST_TEST_INCLUDED_WARNING
# in the same file with the boost test include.
#
# include(BoostTestTargets)
# add_boost_test(<testdriver_name> SOURCES <source1> [<more sources...>]
# [FAIL_REGULAR_EXPRESSION <additional fail regex>]
# [LAUNCHER <generic launcher script>]
# [LIBRARIES <library> [<library>...]]
# [RESOURCES <resource> [<resource>...]]
# [TESTS <testcasename> [<testcasename>...]])
#
# If for some reason you need access to the executable target created,
# it can be found in ${${testdriver_name}_TARGET_NAME} as specified when
# you called add_boost_test
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Requires:
# GetForceIncludeDefinitions
# CopyResourcesToBuildTree
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__add_boost_test)
return()
endif()
set(__add_boost_test YES)
set(BOOST_TEST_TARGET_PREFIX "test")
if(NOT Boost_FOUND)
find_package(Boost 1.34.0 QUIET)
endif()
if("${Boost_VERSION}0" LESS "1034000")
set(_shared_msg
"NOTE: boost::test-based targets and tests cannot "
"be added: boost >= 1.34.0 required but not found. "
"(found: '${Boost_VERSION}'; want >=103400) ")
if(BUILD_TESTING)
message(FATAL_ERROR
${_shared_msg}
"You may disable BUILD_TESTING to continue without the "
"tests.")
else()
message(STATUS
${_shared_msg}
"BUILD_TESTING disabled, so continuing anyway.")
endif()
endif()
include(GetForceIncludeDefinitions)
include(CopyResourcesToBuildTree)
if(Boost_FOUND AND NOT "${Boost_VERSION}0" LESS "1034000")
set(_boosttesttargets_libs)
set(_boostConfig "BoostTestTargetsIncluded.h")
if(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY)
find_package(Boost 1.34.0 QUIET COMPONENTS unit_test_framework)
endif()
if(Boost_UNIT_TEST_FRAMEWORK_LIBRARY)
set(_boosttesttargets_libs "${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}")
if(Boost_USE_STATIC_LIBS)
set(_boostConfig "BoostTestTargetsStatic.h")
else()
set(_boostConfig "BoostTestTargetsDynamic.h")
endif()
endif()
get_filename_component(_moddir ${CMAKE_CURRENT_LIST_FILE} PATH)
configure_file("${_moddir}/${_boostConfig}"
"${CMAKE_CURRENT_BINARY_DIR}/BoostTestTargetConfig.h"
COPYONLY)
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
endif()
function(add_boost_test _name)
if(NOT BUILD_TESTING)
return()
endif()
# parse arguments
set(_nowhere)
set(_curdest _nowhere)
set(_val_args
SOURCES
FAIL_REGULAR_EXPRESSION
LAUNCHER
LIBRARIES
RESOURCES
TESTS)
set(_bool_args
USE_COMPILED_LIBRARY)
foreach(_arg ${_val_args} ${_bool_args})
set(${_arg})
endforeach()
foreach(_element ${ARGN})
list(FIND _val_args "${_element}" _val_arg_find)
list(FIND _bool_args "${_element}" _bool_arg_find)
if("${_val_arg_find}" GREATER "-1")
set(_curdest "${_element}")
elseif("${_bool_arg_find}" GREATER "-1")
set("${_element}" ON)
set(_curdest _nowhere)
else()
list(APPEND ${_curdest} "${_element}")
endif()
endforeach()
if(_nowhere)
message(FATAL_ERROR "Syntax error in use of add_boost_test!")
endif()
if(NOT SOURCES)
message(FATAL_ERROR
"Syntax error in use of add_boost_test: at least one source file required!")
endif()
if(Boost_FOUND AND NOT "${Boost_VERSION}0" LESS "1034000")
include_directories(${Boost_INCLUDE_DIRS})
set(includeType)
foreach(src ${SOURCES})
file(READ ${src} thefile)
if("${thefile}" MATCHES ".*BoostTestTargetConfig.h.*")
set(includeType CONFIGURED)
set(includeFileLoc ${src})
break()
elseif("${thefile}" MATCHES ".*boost/test/included/unit_test.hpp.*")
set(includeType INCLUDED)
set(includeFileLoc ${src})
set(_boosttesttargets_libs) # clear this out - linking would be a bad idea
if(NOT
"${thefile}"
MATCHES
".*OVERRIDE_BOOST_TEST_INCLUDED_WARNING.*")
message("Please replace the include line in ${src} with this alternate include line instead:")
message(" \#include <BoostTestTargetConfig.h>")
message("Once you've saved your changes, re-run CMake. (See BoostTestTargets.cmake for more info)")
endif()
break()
endif()
endforeach()
if(NOT _boostTestTargetsNagged${_name} STREQUAL "${includeType}")
if("includeType" STREQUAL "CONFIGURED")
message(STATUS
"Test '${_name}' uses the CMake-configurable form of the boost test framework - congrats! (Including File: ${includeFileLoc})")
elseif("${includeType}" STREQUAL "INCLUDED")
message("In test '${_name}': ${includeFileLoc} uses the 'included' form of the boost unit test framework.")
else()
message("In test '${_name}': Didn't detect the CMake-configurable boost test include.")
message("Please replace your existing boost test include in that test with the following:")
message(" \#include <BoostTestTargetConfig.h>")
message("Once you've saved your changes, re-run CMake. (See BoostTestTargets.cmake for more info)")
endif()
endif()
set(_boostTestTargetsNagged${_name}
"${includeType}"
CACHE
INTERNAL
""
FORCE)
if(RESOURCES)
list(APPEND SOURCES ${RESOURCES})
endif()
# Generate a unique target name, using the relative binary dir
# and provided name. (transform all / into _ and remove all other
# non-alphabet characters)
file(RELATIVE_PATH
targetpath
"${CMAKE_BINARY_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}")
string(REGEX REPLACE "[^A-Za-z/_]" "" targetpath "${targetpath}")
string(REPLACE "/" "_" targetpath "${targetpath}")
set(_target_name ${BOOST_TEST_TARGET_PREFIX}-${targetpath}-${_name})
set(${_name}_TARGET_NAME "${_target_name}" PARENT_SCOPE)
# Build the test.
add_executable(${_target_name} ${SOURCES})
list(APPEND LIBRARIES ${_boosttesttargets_libs})
if(LIBRARIES)
target_link_libraries(${_target_name} ${LIBRARIES})
endif()
if(RESOURCES)
set_property(TARGET ${_target_name} PROPERTY RESOURCE ${RESOURCES})
copy_resources_to_build_tree(${_target_name})
endif()
if(NOT Boost_TEST_FLAGS)
# set(Boost_TEST_FLAGS --catch_system_error=yes --output_format=XML)
set(Boost_TEST_FLAGS --catch_system_error=yes)
endif()
# TODO: Figure out why only recent boost handles individual test running properly
if(LAUNCHER)
set(_test_command ${LAUNCHER} "\$<TARGET_FILE:${_target_name}>")
else()
set(_test_command ${_target_name})
endif()
if(TESTS AND ( "${Boost_VERSION}" VERSION_GREATER "103799" ))
foreach(_test ${TESTS})
add_test(
${_name}-${_test}
${_test_command} --run_test=${_test} ${Boost_TEST_FLAGS}
)
if(FAIL_REGULAR_EXPRESSION)
set_tests_properties(${_name}-${_test}
PROPERTIES
FAIL_REGULAR_EXPRESSION
"${FAIL_REGULAR_EXPRESSION}")
endif()
endforeach()
else()
add_test(
${_name}-boost_test
${_test_command} ${Boost_TEST_FLAGS}
)
if(FAIL_REGULAR_EXPRESSION)
set_tests_properties(${_name}-boost_test
PROPERTIES
FAIL_REGULAR_EXPRESSION
"${FAIL_REGULAR_EXPRESSION}")
endif()
endif()
# CppCheck the test if we can.
if(COMMAND add_cppcheck)
add_cppcheck(${_target_name} STYLE UNUSED_FUNCTIONS)
endif()
endif()
endfunction()

View File

@@ -0,0 +1,8 @@
// Small header computed by CMake to set up boost test.
// include AFTER #define BOOST_TEST_MODULE whatever
// but before any other boost test includes.
// Using the Boost UTF dynamic library
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

View File

@@ -0,0 +1,7 @@
// Small header computed by CMake to set up boost test.
// include AFTER #define BOOST_TEST_MODULE whatever
// but before any other boost test includes.
// Using the Boost UTF included framework
#include <boost/test/included/unit_test.hpp>

View File

@@ -0,0 +1,7 @@
// Small header computed by CMake to set up boost test.
// include AFTER #define BOOST_TEST_MODULE whatever
// but before any other boost test includes.
// Using the Boost UTF static library
#include <boost/test/unit_test.hpp>

View File

@@ -0,0 +1,83 @@
# - Copy the resources your app needs to the build tree.
#
# copy_resources_to_build_tree(<target_name>)
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__copy_resources_to_build_tree)
return()
endif()
set(__copy_resources_to_build_tree YES)
function(copy_resources_to_build_tree _target)
get_target_property(_resources ${_target} RESOURCE)
if(NOT _resources)
# Bail if no resources
message(STATUS
"Told to copy resources for target ${_target}, but "
"no resources are set!")
return()
endif()
get_target_property(_path ${_target} LOCATION)
get_filename_component(_path "${_path}" PATH)
if(NOT MSVC AND NOT "${CMAKE_GENERATOR}" MATCHES "Makefiles")
foreach(_config ${CMAKE_CONFIGURATION_TYPES})
get_target_property(_path${_config} ${_target} LOCATION_${_config})
get_filename_component(_path${_config} "${_path${_config}}" PATH)
add_custom_command(TARGET ${_target}
POST_BUILD
COMMAND
${CMAKE_COMMAND}
ARGS -E make_directory "${_path${_config}}/"
COMMENT "Creating directory ${_path${_config}}/")
endforeach()
endif()
foreach(_res ${_resources})
if(NOT IS_ABSOLUTE "${_res}")
get_filename_component(_res "${_res}" ABSOLUTE)
endif()
get_filename_component(_name "${_res}" NAME)
if(MSVC)
# Working dir is solution file dir, not exe file dir.
add_custom_command(TARGET ${_target}
POST_BUILD
COMMAND
${CMAKE_COMMAND}
ARGS -E copy "${_res}" "${CMAKE_BINARY_DIR}/"
COMMENT "Copying ${_name} to ${CMAKE_BINARY_DIR}/ for MSVC")
else()
if("${CMAKE_GENERATOR}" MATCHES "Makefiles")
add_custom_command(TARGET ${_target}
POST_BUILD
COMMAND
${CMAKE_COMMAND}
ARGS -E copy "${_res}" "${_path}/"
COMMENT "Copying ${_name} to ${_path}/")
else()
foreach(_config ${CMAKE_CONFIGURATION_TYPES})
add_custom_command(TARGET ${_target}
POST_BUILD
COMMAND
${CMAKE_COMMAND}
ARGS -E copy "${_res}" "${_path${_config}}"
COMMENT "Copying ${_name} to ${_path${_config}}")
endforeach()
endif()
endif()
endforeach()
endfunction()

View File

@@ -1,83 +0,0 @@
# Find Subversion client libraries, and dependencies
# including APR (Apache Portable Runtime)
include (CheckFunctionExists)
include (CheckIncludeFile)
include (CheckLibraryExists)
macro(find_static_component comp libs)
# account for alternative Windows svn distribution naming
if(MSVC)
set(compLib "lib${comp}")
else(MSVC)
set(compLib "${comp}")
endif(MSVC)
string(TOUPPER "${comp}" compLibBase)
set( compLibName ${compLibBase}_LIBRARY )
# NO_DEFAULT_PATH is important on Mac - we need to ensure subversion
# libraires in dist/ or Macports are picked over the Apple version
# in /usr, since that's what we will ship.
# On other platforms we do need default paths though, i.e. since Linux
# distros may use architecture-specific directories (like
# /usr/lib/x86_64-linux-gnu) which we cannot hardcode/guess here.
FIND_LIBRARY(${compLibName}
if(APPLE)
NO_DEFAULT_PATH
endif(APPLE)
NAMES ${compLib}
HINTS $ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib
PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
PATHS ${ADDITIONAL_LIBRARY_PATHS}
)
list(APPEND ${libs} ${${compLibName}})
endmacro()
find_program(HAVE_APR_CONFIG apr-1-config)
if(HAVE_APR_CONFIG)
execute_process(COMMAND apr-1-config --cppflags --includes
OUTPUT_VARIABLE APR_CFLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND apr-1-config --link-ld
OUTPUT_VARIABLE RAW_APR_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE)
# clean up some vars, or other CMake pieces complain
string(STRIP "${RAW_APR_LIBS}" APR_LIBS)
else(HAVE_APR_CONFIG)
message(STATUS "apr-1-config not found, implement manual search for APR")
endif(HAVE_APR_CONFIG)
if(HAVE_APR_CONFIG OR MSVC)
find_path(LIBSVN_INCLUDE_DIR svn_client.h
NO_DEFAULT_PATH
HINTS
$ENV{LIBSVN_DIR} ${CMAKE_INSTALL_PREFIX} ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include
PATH_SUFFIXES include/subversion-1
PATHS
/opt/local
/usr/local
/usr
)
set(LIBSVN_LIBRARIES "")
if (MSVC)
find_static_component("apr-1" LIBSVN_LIBRARIES)
else (MSVC)
list(APPEND LIBSVN_LIBRARIES ${APR_LIBS})
endif (MSVC)
find_static_component("svn_client-1" LIBSVN_LIBRARIES)
find_static_component("svn_subr-1" LIBSVN_LIBRARIES)
find_static_component("svn_ra-1" LIBSVN_LIBRARIES)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBSVN DEFAULT_MSG LIBSVN_LIBRARIES LIBSVN_INCLUDE_DIR)
if(NOT LIBSVN_FOUND)
set(LIBSVN_LIBRARIES "")
endif(NOT LIBSVN_FOUND)
endif(HAVE_APR_CONFIG OR MSVC)

View File

@@ -0,0 +1,44 @@
# - Get the platform-appropriate flags to add to force inclusion of a file
#
# The most common use of this is to use a generated config.h-type file
# placed out of the source tree in all files.
#
# get_force_include_definitions(var forcedincludefiles...) -
# where var is the name of your desired output variable, and everything
# else is a source file to forcibly include.
# a list item to be filtered.
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__get_force_include_definitions)
return()
endif()
set(__get_force_include_definitions YES)
function(get_force_include_definitions var)
set(_flagprefix)
if(CMAKE_COMPILER_IS_GNUCXX)
set(_flag "-include")
elseif(MSVC)
set(_flag "/FI")
else()
message(SEND_ERROR "You don't seem to be using MSVC or GCC, but")
message(SEND_ERROR "the project called get_force_include_definitions.")
message(SEND_ERROR "Contact this project with the name of your")
message(FATAL_ERROR "compiler and preferably the flag to force includes")
endif()
set(_out)
foreach(_item ${ARGN})
list(APPEND _out "${_flag} \"${_item}\"")
endforeach()
set(${var} "${_out}" PARENT_SCOPE)
endfunction()

2592
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ foreach( mylibfolder
threads
timing
xml
package
)
add_subdirectory(${mylibfolder})
@@ -44,52 +45,29 @@ install (FILES ${HEADERS} DESTINATION include/simgear/)
get_property(coreSources GLOBAL PROPERTY CORE_SOURCES)
get_property(sceneSources GLOBAL PROPERTY SCENE_SOURCES)
get_property(publicHeaders GLOBAL PROPERTY PUBLIC_HEADERS)
if(LIBSVN_FOUND)
add_definitions(${APR_CFLAGS})
include_directories(${LIBSVN_INCLUDE_DIR})
endif()
get_property(localExpatSources GLOBAL PROPERTY LOCAL_EXPAT_SOURCES)
if(SIMGEAR_SHARED)
message(STATUS "Library building mode: SHARED LIBRARIES")
add_library(SimGearCore SHARED ${coreSources})
add_library(SimGearCore SHARED ${coreSources} ${localExpatSources})
# set_property(TARGET SimGearCore PROPERTY FRAMEWORK 1)
# message(STATUS "public header: ${publicHeaders}")
# set_property(TARGET SimGearCore PROPERTY PUBLIC_HEADER "${publicHeaders}")
set_property(TARGET SimGearCore PROPERTY LINKER_LANGUAGE CXX)
set_property(TARGET SimGearCore PROPERTY VERSION ${SIMGEAR_VERSION})
set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY}
${EXPAT_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT})
if(LIBSVN_FOUND)
target_link_libraries(SimGearCore ${LIBSVN_LIBRARIES})
endif(LIBSVN_FOUND)
install(TARGETS SimGearCore EXPORT SimGearCoreConfig LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(EXPORT SimGearCoreConfig DESTINATION share/SimGearCore)
if(NOT SIMGEAR_HEADLESS)
add_library(SimGearScene SHARED ${sceneSources})
# set_property(TARGET SimGearScene PROPERTY FRAMEWORK 1)
# set_property(TARGET SimGearScene PROPERTY PUBLIC_HEADER "${publicHeaders}")
set_property(TARGET SimGearScene PROPERTY LINKER_LANGUAGE CXX)
set_property(TARGET SimGearScene PROPERTY VERSION ${SIMGEAR_VERSION})
set_property(TARGET SimGearScene PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
target_link_libraries(SimGearScene
SimGearCore
${ZLIB_LIBRARY}
${OPENSCENEGRAPH_LIBRARIES}
${OPENAL_LIBRARY}
${OPENGL_LIBRARY}
${JPEG_LIBRARY})
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
# EXPORT SimGearSceneConfig
install(TARGETS SimGearScene LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} )
# install(EXPORT SimGearSceneConfig DESTINATION share/SimGearScene)
endif()
install(TARGETS SimGearCore LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
else()
message(STATUS "Library building mode: STATIC LIBRARIES")
@@ -111,7 +89,7 @@ else()
source_group("${name}\\Headers" FILES ${g2})
endforeach()
add_library(SimGearCore STATIC ${coreSources})
add_library(SimGearCore STATIC ${coreSources} ${localExpatSources})
install(TARGETS SimGearCore ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if(NOT SIMGEAR_HEADLESS)
@@ -138,6 +116,24 @@ else()
endif(NOT SIMGEAR_HEADLESS)
endif(SIMGEAR_SHARED)
target_link_libraries(SimGearCore
${ZLIB_LIBRARY}
${RT_LIBRARY}
${DL_LIBRARY}
${EXPAT_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
${COCOA_LIBRARY})
if(NOT SIMGEAR_HEADLESS)
target_link_libraries(SimGearScene
SimGearCore
${ZLIB_LIBRARY}
${OPENSCENEGRAPH_LIBRARIES}
${OPENAL_LIBRARY}
${OPENGL_LIBRARY}
${JPEG_LIBRARY})
endif()
if(ENABLE_RTI)
# Ugly first aid to make hla compile agian
set_property(SOURCE hla/RTI13InteractionClass.cxx hla/RTI13ObjectClass.cxx

View File

@@ -4,4 +4,13 @@ include (SimGearComponent)
set(HEADERS newbucket.hxx)
set(SOURCES newbucket.cxx)
simgear_component(bucket bucket "${SOURCES}" "${HEADERS}")
simgear_component(bucket bucket "${SOURCES}" "${HEADERS}")
if(ENABLE_TESTS)
add_executable(test_bucket test_bucket.cxx)
add_test(test_bucket ${EXECUTABLE_OUTPUT_PATH}/test_bucket)
target_link_libraries(test_bucket ${TEST_LIBS})
endif(ENABLE_TESTS)

View File

@@ -27,36 +27,56 @@
# include <simgear_config.h>
#endif
#include <math.h>
#include <cmath>
#include <cstdio> // some platforms need this for ::snprintf
#include <iostream>
#include <simgear/misc/sg_path.hxx>
#include <simgear/debug/logstream.hxx>
#include "newbucket.hxx"
// default constructor
SGBucket::SGBucket() {
SGBucket::SGBucket() :
lon(-1000),
lat(-1000),
x(0),
y(0)
{
}
bool SGBucket::isValid() const
{
// The most northerly valid latitude is 89, not 90. There is no tile
// whose *bottom* latitude is 90. Similar there is no tile whose left egde
// is 180 longitude.
return (lon >= -180) &&
(lon < 180) &&
(lat >= -90) &&
(lat < 90) &&
(x < 8) && (y < 8);
}
void SGBucket::make_bad()
{
lon = -1000;
lat = -1000;
}
#ifndef NO_DEPRECATED_API
// constructor for specified location
SGBucket::SGBucket(const double dlon, const double dlat) {
set_bucket(dlon, dlat);
}
#endif
SGBucket::SGBucket(const SGGeod& geod) {
set_bucket(geod);
innerSet(geod.getLongitudeDeg(),
geod.getLatitudeDeg());
}
// create an impossible bucket if false
SGBucket::SGBucket(const bool is_good) {
set_bucket(0.0, 0.0);
if ( !is_good ) {
lon = -1000;
}
}
// Parse a unique scenery tile index and find the lon, lat, x, and y
SGBucket::SGBucket(const long int bindex) {
long int index = bindex;
@@ -75,48 +95,59 @@ SGBucket::SGBucket(const long int bindex) {
x = index;
}
/* Calculate the greatest integral value less than
* or equal to the given value (floor(x)),
* but attribute coordinates close to the boundary to the next
* (increasing) integral
*/
static int floorWithEpsilon(double x)
{
return static_cast<int>(floor(x + SG_EPSILON));
}
#ifndef NO_DEPRECATED_API
void SGBucket::set_bucket(double dlon, double dlat)
{
innerSet(dlon, dlat);
}
void SGBucket::set_bucket(const SGGeod& geod)
{
innerSet(geod.getLongitudeDeg(), geod.getLatitudeDeg());
}
#endif
// Set the bucket params for the specified lat and lon
void SGBucket::set_bucket( double *lonlat ) {
set_bucket( lonlat[0], lonlat[1] );
}
// Set the bucket params for the specified lat and lon
void SGBucket::set_bucket( double dlon, double dlat ) {
void SGBucket::innerSet( double dlon, double dlat )
{
if ((dlon < -180.0) || (dlon >= 180.0)) {
SG_LOG(SG_TERRAIN, SG_WARN, "SGBucket::set_bucket: passed longitude:" << dlon);
dlon = SGMiscd::normalizePeriodic(-180.0, 180.0, dlon);
}
if ((dlat < -90.0) || (dlat > 90.0)) {
SG_LOG(SG_TERRAIN, SG_WARN, "SGBucket::set_bucket: passed latitude" << dlat);
dlat = SGMiscd::clip(dlat, -90.0, 90.0);
}
//
// latitude first
// longitude first
//
double span = sg_bucket_span( dlat );
double diff = dlon - (double)(int)dlon;
// cout << "diff = " << diff << " span = " << span << endl;
/* Calculate the greatest integral longitude less than
* or equal to the given longitude (floor(dlon)),
* but attribute coordinates near the east border
* to the next tile.
*/
if ( (dlon >= 0) || (fabs(diff) < SG_EPSILON) ) {
lon = (int)dlon;
} else {
lon = (int)dlon - 1;
}
// we do NOT need to special case lon=180 here, since
// normalizePeriodic will never return 180; it will
// return -180, which is what we want.
lon = floorWithEpsilon(dlon);
// find subdivision or super lon if needed
if ( span < SG_EPSILON ) {
/* sg_bucket_span() never returns 0.0
* or anything near it, so this really
* should not occur at any time.
*/
// polar cap
lon = 0;
x = 0;
} else if ( span <= 1.0 ) {
if ( span <= 1.0 ) {
/* We have more than one tile per degree of
* longitude, so we need an x offset.
*/
x = (int)((dlon - lon) / span);
x = floorWithEpsilon((dlon - lon) / span);
} else {
/* We have one or more degrees per tile,
* so we need to find the base longitude
@@ -129,48 +160,28 @@ void SGBucket::set_bucket( double dlon, double dlat ) {
*
* That way, the Greenwich Meridian is always
* a tile border.
*
* This gets us into trouble with the polar caps,
* which have width 360 and thus either span
* the range from 0 to 360 or from -360 to 0
* degrees, depending on whether lon is positive
* or negative!
*
* We also get into trouble with the 8 degree tiles
* north of 88N and south of 88S, because the west-
* and east-most tiles in that range will cover 184W
* to 176W and 176E to 184E respectively, with their
* center at 180E/W!
*/
lon=(int)floor(floor((lon+SG_EPSILON)/span)*span);
/* Correct the polar cap issue */
if ( lon < -180 ) {
lon = -180;
}
x = 0;
lon=static_cast<int>(floor(lon / span) * span);
x = 0;
}
//
// then latitude
//
diff = dlat - (double)(int)dlat;
/* Again, a modified floor() function (see longitude) */
if ( (dlat >= 0) || (fabs(diff) < SG_EPSILON) ) {
lat = (int)dlat;
lat = floorWithEpsilon(dlat);
// special case when passing in the north pole point (possibly due to
// clipping latitude above). Ensures we generate a valid bucket in this
// scenario
if (lat == 90) {
lat = 89;
y = 7;
} else {
lat = (int)dlat - 1;
/* Latitude base and offset are easier, as
* tiles always are 1/8 degree of latitude wide.
*/
y = floorWithEpsilon((dlat - lat) * 8);
}
/* Latitude base and offset are easier, as
* tiles always are 1/8 degree of latitude wide.
*/
y = (int)((dlat - lat) * 8);
}
void SGBucket::set_bucket(const SGGeod& geod)
{
set_bucket(geod.getLongitudeDeg(), geod.getLatitudeDeg());
}
// Build the path name for this bucket
@@ -212,7 +223,7 @@ std::string SGBucket::gen_base_path() const {
main_lat *= -1;
}
sprintf(raw_path, "%c%03d%c%02d/%c%03d%c%02d",
::snprintf(raw_path, 256, "%c%03d%c%02d/%c%03d%c%02d",
hem, top_lon, pole, top_lat,
hem, main_lon, pole, main_lat);
@@ -224,17 +235,6 @@ std::string SGBucket::gen_base_path() const {
// return width of the tile in degrees
double SGBucket::get_width() const {
if (lon==-180 && (lat==-89 || lat==88) ) {
/* Normally the tile at 180W in 88N and 89S
* would cover 184W to 176W and the next
* on the east side starts at 176W.
* To correct, make this a special tile
* from 180W to 176W with 4 degrees width
* instead of the normal 8 degrees at
* that latitude.
*/
return 4.0;
}
return sg_bucket_span( get_center_lat() );
}
@@ -244,17 +244,32 @@ double SGBucket::get_height() const {
return SG_BUCKET_SPAN;
}
// return width of the tile in meters
double SGBucket::get_width_m() const {
double clat = (int)get_center_lat();
if ( clat > 0 ) {
clat = (int)clat + 0.5;
} else {
clat = (int)clat - 0.5;
double SGBucket::get_highest_lat() const
{
unsigned char adjustedY = y;
if (lat >= 0) {
// tile is north of the equator, so we want the top edge. Add one
// to y to achieve this.
++adjustedY;
}
double clat_rad = clat * SGD_DEGREES_TO_RADIANS;
return lat + (adjustedY / 8.0);
}
// return width of the tile in meters. This function is used by the
// tile-manager to estimate how many tiles are in the view distance, so
// we care about the smallest width, which occurs at the highest latitude.
double SGBucket::get_width_m() const
{
double clat_rad = get_highest_lat() * SGD_DEGREES_TO_RADIANS;
double cos_lat = cos( clat_rad );
if (fabs(cos_lat) < SG_EPSILON) {
// happens for polar tiles, since we pass in a latitude of 90
// return an arbitrary small value so all tiles are loaded
return 10.0;
}
double local_radius = cos_lat * SG_EQUATORIAL_RADIUS_M;
double local_perimeter = local_radius * SGD_2PI;
double degree_width = local_perimeter / 360.0;
@@ -271,7 +286,41 @@ double SGBucket::get_height_m() const {
return SG_BUCKET_SPAN * degree_height;
}
SGBucket SGBucket::sibling(int dx, int dy) const
{
if (!isValid()) {
SG_LOG(SG_TERRAIN, SG_WARN, "SGBucket::sibling: requesting sibling of invalid bucket");
return SGBucket();
}
double clat = get_center_lat() + dy * SG_BUCKET_SPAN;
// return invalid here instead of clipping, so callers can discard
// invalid buckets without having to check if it's an existing one
if ((clat < -90.0) || (clat > 90.0)) {
return SGBucket();
}
// find the lon span for the new latitude
double span = sg_bucket_span( clat );
double tmp = get_center_lon() + dx * span;
tmp = SGMiscd::normalizePeriodic(-180.0, 180.0, tmp);
SGBucket b;
b.innerSet(tmp, clat);
return b;
}
std::string SGBucket::gen_index_str() const
{
char tmp[20];
::snprintf(tmp, 20, "%ld",
(((long)lon + 180) << 14) + ((lat + 90) << 6)
+ (y << 3) + x);
return (std::string)tmp;
}
#ifndef NO_DEPRECATED_API
// find the bucket which is offset by the specified tile units in the
// X & Y direction. We need the current lon and lat to resolve
// ambiguities when going from a wider tile to a narrower one above or
@@ -298,7 +347,7 @@ SGBucket sgBucketOffset( double dlon, double dlat, int dx, int dy ) {
return result;
}
#endif
// calculate the offset between two buckets
void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy ) {
@@ -356,4 +405,25 @@ void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy ) {
#endif
}
void sgGetBuckets( const SGGeod& min, const SGGeod& max, std::vector<SGBucket>& list ) {
double lon, lat, span;
for (lat = min.getLatitudeDeg(); lat < max.getLatitudeDeg()+SG_BUCKET_SPAN; lat += SG_BUCKET_SPAN) {
span = sg_bucket_span( lat );
for (lon = min.getLongitudeDeg(); lon <= max.getLongitudeDeg(); lon += span)
{
SGBucket b(SGGeod::fromDeg(lon, lat));
if (!b.isValid()) {
continue;
}
list.push_back(b);
}
}
}
std::ostream& operator<< ( std::ostream& out, const SGBucket& b )
{
return out << b.lon << ":" << (int)b.x << ", " << b.lat << ":" << (int)b.y;
}

View File

@@ -39,9 +39,11 @@
#include <simgear/math/SGMath.hxx>
#include <cmath>
#include <cstdio> // sprintf()
#include <ostream>
#include <string>
#include <iosfwd>
#include <vector>
// #define NO_DEPRECATED_API
/**
* standard size of a bucket in degrees (1/8 of a degree)
@@ -57,9 +59,7 @@
// return the horizontal tile span factor based on latitude
static double sg_bucket_span( double l ) {
if ( l >= 89.0 ) {
return 360.0;
} else if ( l >= 88.0 ) {
return 8.0;
return 12.0;
} else if ( l >= 86.0 ) {
return 4.0;
} else if ( l >= 83.0 ) {
@@ -80,12 +80,10 @@ static double sg_bucket_span( double l ) {
return 1.0;
} else if ( l >= -86.0 ) {
return 2.0;
} else if ( l >= -88.0 ) {
return 4.0;
} else if ( l >= -89.0 ) {
return 8.0;
return 4.0;
} else {
return 360.0;
return 12.0;
}
}
@@ -102,23 +100,31 @@ class SGBucket {
private:
short lon; // longitude index (-180 to 179)
short lat; // latitude index (-90 to 89)
char x; // x subdivision (0 to 7)
char y; // y subdivision (0 to 7)
unsigned char x; // x subdivision (0 to 7)
unsigned char y; // y subdivision (0 to 7)
void innerSet( double dlon, double dlat );
public:
/**
* Default constructor.
* Default constructor, creates an invalid SGBucket
*/
SGBucket();
/**
* Check if this bucket refers to a valid tile, or not.
*/
bool isValid() const;
#ifndef NO_DEPRECATED_API
/**
* Construct a bucket given a specific location.
* @param dlon longitude specified in degrees
* @param dlat latitude specified in degrees
*/
SGBucket(const double dlon, const double dlat);
#endif
/**
* Construct a bucket given a specific location.
* @param dlon longitude specified in degrees
@@ -126,33 +132,12 @@ public:
*/
SGBucket(const SGGeod& geod);
/** Construct a bucket.
* @param is_good if false, create an invalid bucket. This is
* useful * if you are comparing cur_bucket to last_bucket and
* you want to * make sure last_bucket starts out as something
* impossible.
*/
SGBucket(const bool is_good);
/** Construct a bucket given a unique bucket index number.
* @param bindex unique bucket index
*/
SGBucket(const long int bindex);
/**
* Reset a bucket to represent a new lat and lon
* @param dlon longitude specified in degrees
* @param dlat latitude specified in degrees
*/
void set_bucket( double dlon, double dlat );
/**
* Reset a bucket to represent a new lat and lon
* @param lonlat an array of double[2] holding lon and lat
* (specified) in degrees
*/
void set_bucket( double *lonlat );
#ifndef NO_DEPRECATED_API
/**
* Reset a bucket to represent a new lat and lon
* @param dlon longitude specified in degrees
@@ -160,17 +145,23 @@ public:
*/
void set_bucket(const SGGeod& geod);
/**
* Reset a bucket to represent a new lat and lon
* @param dlon longitude specified in degrees
* @param dlat latitude specified in degrees
*/
void set_bucket( double dlon, double dlat );
#endif
/**
* Create an impossible bucket.
* This is useful if you are comparing cur_bucket to last_bucket
* and you want to make sure last_bucket starts out as something
* impossible.
*/
inline void make_bad() {
set_bucket(0.0, 0.0);
lon = -1000;
}
void make_bad();
/**
* Generate the unique scenery tile index for this bucket
*
@@ -195,14 +186,8 @@ public:
* string form.
* @return tile index in string form
*/
inline std::string gen_index_str() const {
char tmp[20];
std::sprintf(tmp, "%ld",
(((long)lon + 180) << 14) + ((lat + 90) << 6)
+ (y << 3) + x);
return (std::string)tmp;
}
std::string gen_index_str() const;
/**
* Build the base path name for this bucket.
* @return base path in string form
@@ -229,6 +214,13 @@ public:
return lat + y / 8.0 + SG_HALF_BUCKET_SPAN;
}
/**
* @return the highest (furthest from the equator) latitude of this
* tile. This is the top edge for tiles north of the equator, and
* the bottom edge for tiles south
*/
double get_highest_lat() const;
/**
* @return the width of the tile in degrees.
*/
@@ -290,6 +282,11 @@ public:
*/
inline int get_y() const { return y; }
/**
* @return bucket offset from this by dx,dy
*/
SGBucket sibling(int dx, int dy) const;
// friends
friend std::ostream& operator<< ( std::ostream&, const SGBucket& );
@@ -301,7 +298,7 @@ inline bool operator!= (const SGBucket& lhs, const SGBucket& rhs)
return !(lhs == rhs);
}
#ifndef NO_DEPRECATED_API
/**
* \relates SGBucket
* Return the bucket which is offset from the specified dlon, dlat by
@@ -313,6 +310,7 @@ inline bool operator!= (const SGBucket& lhs, const SGBucket& rhs)
* @return offset bucket
*/
SGBucket sgBucketOffset( double dlon, double dlat, int x, int y );
#endif
/**
@@ -326,17 +324,21 @@ SGBucket sgBucketOffset( double dlon, double dlat, int x, int y );
void sgBucketDiff( const SGBucket& b1, const SGBucket& b2, int *dx, int *dy );
/**
* \relates SGBucket
* retrieve a list of buckets in the given bounding box
* @param min min lon,lat of bounding box in degrees
* @param max max lon,lat of bounding box in degrees
* @param list standard vector of buckets within the bounding box
*/
void sgGetBuckets( const SGGeod& min, const SGGeod& max, std::vector<SGBucket>& list );
/**
* Write the bucket lon, lat, x, and y to the output stream.
* @param out output stream
* @param b bucket
*/
inline std::ostream&
operator<< ( std::ostream& out, const SGBucket& b )
{
return out << b.lon << ":" << (int)b.x << ", " << b.lat << ":" << (int)b.y;
}
std::ostream& operator<< ( std::ostream& out, const SGBucket& b );
/**
* Compare two bucket structures for equality.

View File

@@ -0,0 +1,283 @@
/**************************************************************************
* test_bucket.cxx -- unit-tests for SGBucket class
*
* Copyright (C) 2014 James Turner - <zakalawe@mac.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 General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
**************************************************************************/
#include <simgear/compiler.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
using std::cout;
using std::cerr;
using std::endl;
#include <simgear/bucket/newbucket.hxx>
#include <simgear/misc/test_macros.hxx>
void testBucketSpans()
{
COMPARE(sg_bucket_span(0.0), 0.125);
COMPARE(sg_bucket_span(-20), 0.125);
COMPARE(sg_bucket_span(-40), 0.25);
COMPARE(sg_bucket_span(89.9), 12.0);
COMPARE(sg_bucket_span(88.1), 4.0);
COMPARE(sg_bucket_span(-89.9), 12.0);
}
void testBasic()
{
SGBucket b1(5.1, 55.05);
COMPARE(b1.get_chunk_lon(), 5);
COMPARE(b1.get_chunk_lat(), 55);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 0);
COMPARE(b1.gen_index(), 3040320);
COMPARE(b1.gen_base_path(), "e000n50/e005n55");
VERIFY(b1.isValid());
SGBucket b2(-10.1, -43.8);
COMPARE(b2.get_chunk_lon(), -11);
COMPARE(b2.get_chunk_lat(), -44);
COMPARE(b2.get_x(), 3);
COMPARE(b2.get_y(), 1); // latitude chunks numbered bottom to top, it seems
COMPARE(b2.gen_base_path(), "w020s50/w011s44");
VERIFY(b2.isValid());
SGBucket b3(123.48, 9.01);
COMPARE(b3.get_chunk_lon(), 123);
COMPARE(b3.get_chunk_lat(), 9);
COMPARE(b3.get_x(), 3);
COMPARE(b3.get_y(), 0);
COMPARE(b3.gen_base_path(), "e120n00/e123n09");
VERIFY(b3.isValid());
SGBucket defBuck;
VERIFY(!defBuck.isValid());
b3.make_bad();
VERIFY(!b3.isValid());
SGBucket atAntiMeridian(180.0, 12.3);
VERIFY(atAntiMeridian.isValid());
COMPARE(atAntiMeridian.get_chunk_lon(), -180);
COMPARE(atAntiMeridian.get_x(), 0);
SGBucket atAntiMeridian2(-180.0, -78.1);
VERIFY(atAntiMeridian2.isValid());
COMPARE(atAntiMeridian2.get_chunk_lon(), -180);
COMPARE(atAntiMeridian2.get_x(), 0);
// check comparisom operator overload
SGBucket b4(5.11, 55.1);
VERIFY(b1 == b4); // should be equal
VERIFY(b1 == b1);
VERIFY(b1 != defBuck);
VERIFY(b1 != b2);
// check wrapping/clipping of inputs
SGBucket wrapMeridian(-200.0, 45.0);
COMPARE(wrapMeridian.get_chunk_lon(), 160);
SGBucket clipPole(48.9, 91);
COMPARE(clipPole.get_chunk_lat(), 89);
}
void testPolar()
{
SGBucket b1(0.0, 89.92);
SGBucket b2(10.0, 89.96);
COMPARE(b1.get_chunk_lat(), 89);
COMPARE(b1.get_chunk_lon(), 0);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 7);
COMPARE_EP(b1.get_highest_lat(), 90.0);
COMPARE_EP(b1.get_width_m(), 10.0);
COMPARE(b2.get_chunk_lat(), 89);
COMPARE(b2.get_chunk_lon(), 0);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 7);
COMPARE(b1.gen_index(), b2.gen_index());
SGGeod actualNorthPole1 = b1.get_corner(2);
SGGeod actualNorthPole2 = b1.get_corner(3);
COMPARE_EP(actualNorthPole1.getLatitudeDeg(), 90.0);
COMPARE_EP(actualNorthPole1.getLongitudeDeg(), 12.0);
COMPARE_EP(actualNorthPole2.getLatitudeDeg(), 90.0);
COMPARE_EP(actualNorthPole2.getLongitudeDeg(), 0.0);
SGBucket b3(-2, 89.88);
SGBucket b4(-7, 89.88);
COMPARE(b3.gen_index(), b4.gen_index());
// south pole
SGBucket b5(-170, -89.88);
SGBucket b6(-179, -89.88);
COMPARE(b5.get_chunk_lat(), -90);
COMPARE(b5.get_chunk_lon(), -180);
COMPARE(b5.get_x(), 0);
COMPARE(b5.get_y(), 0);
COMPARE(b5.gen_index(), b6.gen_index());
COMPARE_EP(b5.get_highest_lat(), -90.0);
COMPARE_EP(b5.get_width_m(), 10.0);
SGGeod actualSouthPole1 = b5.get_corner(0);
SGGeod actualSouthPole2 = b5.get_corner(1);
COMPARE_EP(actualSouthPole1.getLatitudeDeg(), -90.0);
COMPARE_EP(actualSouthPole1.getLongitudeDeg(), -180);
COMPARE_EP(actualSouthPole2.getLatitudeDeg(), -90.0);
COMPARE_EP(actualSouthPole2.getLongitudeDeg(), -168);
SGBucket b7(200, 89.88);
COMPARE(b7.get_chunk_lon(), -168);
}
// test the tiles just below the pole (between 86 & 89 degrees N/S)
void testNearPolar()
{
SGBucket b1(1, 88.5);
SGBucket b2(-1, 88.8);
COMPARE(b1.get_chunk_lon(), 0);
COMPARE(b1.get_chunk_lat(), 88);
VERIFY(b1.gen_index() != b2.gen_index());
SGBucket b3(176.1, 88.5);
COMPARE(b3.get_chunk_lon(), 176);
SGBucket b4(-178, 88.5);
COMPARE(b4.get_chunk_lon(), -180);
}
void testOffset()
{
// bucket just below the 22 degree cutoff, so the next tile north
// is twice the width
SGBucket b1(-59.8, 21.9);
COMPARE(b1.get_chunk_lat(), 21);
COMPARE(b1.get_chunk_lon(), -60);
COMPARE(b1.get_x(), 1);
COMPARE(b1.get_y(), 7);
// offset vertically
SGBucket b2(b1.sibling(0, 1));
COMPARE(b2.get_chunk_lat(), 22);
COMPARE(b2.get_chunk_lon(), -60);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 0);
COMPARE(b2.gen_index(), sgBucketOffset(-59.8, 21.9, 0, 1));
// offset vertically and horizontally. We compute horizontal (x)
// movement at the target latitude, so this should move 0.25 * -3 degrees,
// NOT 0.125 * -3 degrees.
SGBucket b3(b1.sibling(-3, 1));
COMPARE(b3.get_chunk_lat(), 22);
COMPARE(b3.get_chunk_lon(), -61);
COMPARE(b3.get_x(), 1);
COMPARE(b3.get_y(), 0);
COMPARE(b3.gen_index(), sgBucketOffset(-59.8, 21.9, -3, 1));
}
void testPolarOffset()
{
SGBucket b1(-11.7, -89.6);
COMPARE(b1.get_chunk_lat(), -90);
COMPARE(b1.get_chunk_lon(), -12);
COMPARE(b1.get_x(), 0);
COMPARE(b1.get_y(), 3);
// offset horizontally
SGBucket b2(b1.sibling(-2, 0));
COMPARE(b2.get_chunk_lat(), -90);
COMPARE(b2.get_chunk_lon(), -36);
COMPARE(b2.get_x(), 0);
COMPARE(b2.get_y(), 3);
COMPARE(b2.gen_index(), sgBucketOffset(-11.7, -89.6, -2, 0));
// offset and wrap
SGBucket b3(-170, 89.1);
SGBucket b4(b3.sibling(-1, 0));
COMPARE(b4.get_chunk_lat(), 89);
COMPARE(b4.get_chunk_lon(), 168);
COMPARE(b4.get_x(), 0);
COMPARE(b4.get_y(), 0);
COMPARE(b4.gen_index(), sgBucketOffset(-170, 89.1, -1, 0));
SGBucket b5(177, 87.3);
SGBucket b6(b5.sibling(1, 1));
COMPARE(b6.get_chunk_lat(), 87);
COMPARE(b6.get_chunk_lon(), -180);
COMPARE(b6.get_x(), 0);
COMPARE(b6.get_y(), 3);
COMPARE(b6.gen_index(), sgBucketOffset(177, 87.3, 1, 1));
// offset vertically towards the pole
SGBucket b7(b1.sibling(0, -5));
VERIFY(!b7.isValid());
VERIFY(!SGBucket(0, 90).sibling(0, 1).isValid());
}
// test behaviour of bucket-offset near the anti-meridian (180-meridian)
void testOffsetWrap()
{
// near the equator
SGBucket b1(-179.8, 16.8);
COMPARE(b1.get_chunk_lat(), 16);
COMPARE(b1.get_chunk_lon(), -180);
COMPARE(b1.get_x(), 1);
COMPARE(b1.get_y(), 6);
SGBucket b2(b1.sibling(-2, 0));
COMPARE(b2.get_chunk_lat(), 16);
COMPARE(b2.get_chunk_lon(), 179);
COMPARE(b2.get_x(), 7);
COMPARE(b2.get_y(), 6);
COMPARE(b2.gen_index(), sgBucketOffset(-179.8, 16.8, -2, 0));
}
int main(int argc, char* argv[])
{
testBucketSpans();
testBasic();
testPolar();
testNearPolar();
testOffset();
testOffsetWrap();
testPolarOffset();
cout << "all tests passed OK" << endl;
return 0; // passed
}

View File

@@ -4,14 +4,14 @@ set(HEADERS
canvas_fwd.hxx
Canvas.hxx
CanvasEvent.hxx
CanvasEventListener.hxx
CanvasEventManager.hxx
CanvasEventTypes.hxx
CanvasEventVisitor.hxx
CanvasMgr.hxx
CanvasObjectPlacement.hxx
CanvasPlacement.hxx
CanvasSystemAdapter.hxx
MouseEvent.hxx
CanvasWindow.hxx
ODGauge.hxx
VGInitOperation.hxx
)
@@ -19,16 +19,19 @@ set(HEADERS
set(SOURCES
Canvas.cxx
CanvasEvent.cxx
CanvasEventListener.cxx
CanvasEventManager.cxx
CanvasEventVisitor.cxx
CanvasMgr.cxx
CanvasObjectPlacement.cxx
CanvasPlacement.cxx
CanvasWindow.cxx
ODGauge.cxx
VGInitOperation.cxx
)
add_subdirectory(ShivaVG/src)
add_subdirectory(elements)
add_subdirectory(events)
add_subdirectory(layout)
simgear_scene_component(canvas canvas "${SOURCES}" "${HEADERS}")

View File

@@ -19,8 +19,8 @@
#include "Canvas.hxx"
#include "CanvasEventManager.hxx"
#include "CanvasEventVisitor.hxx"
#include <simgear/canvas/MouseEvent.hxx>
#include <simgear/canvas/CanvasPlacement.hxx>
#include "CanvasPlacement.hxx"
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
@@ -31,7 +31,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
#include <iostream>
namespace simgear
{
@@ -49,8 +48,12 @@ namespace canvas
void Canvas::CullCallback::operator()( osg::Node* node,
osg::NodeVisitor* nv )
{
if( (nv->getTraversalMask() & simgear::MODEL_BIT) && !_canvas.expired() )
_canvas.lock()->enableRendering();
if( (nv->getTraversalMask() & simgear::MODEL_BIT) )
{
CanvasPtr canvas = _canvas.lock();
if( canvas )
canvas->enableRendering();
}
traverse(node, nv);
}
@@ -73,6 +76,13 @@ namespace canvas
{
_status = 0;
setStatusFlags(MISSING_SIZE_X | MISSING_SIZE_Y);
_root_group.reset( new Group(this, _node) );
// Remove automatically created property listener as we forward them on our
// own
_root_group->removeListener();
_cull_callback = new CullCallback(this);
}
//----------------------------------------------------------------------------
@@ -82,16 +92,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
void Canvas::onDestroy()
{
_system_adapter = system_adapter;
_texture.setSystemAdapter(system_adapter);
}
//----------------------------------------------------------------------------
SystemAdapterPtr Canvas::getSystemAdapter() const
{
return _system_adapter;
if( _root_group )
{
_root_group->clearEventListener();
_root_group->onDestroy();
}
}
//----------------------------------------------------------------------------
@@ -107,7 +114,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Canvas::addDependentCanvas(const CanvasWeakPtr& canvas)
bool Canvas::isInit() const
{
return _texture.serviceable();
}
//----------------------------------------------------------------------------
void Canvas::addParentCanvas(const CanvasWeakPtr& canvas)
{
if( canvas.expired() )
{
@@ -115,28 +128,75 @@ namespace canvas
(
SG_GENERAL,
SG_WARN,
"Canvas::addDependentCanvas: got an expired Canvas dependent on "
<< _node->getPath()
"Canvas::addParentCanvas(" << _node->getPath(true) << "): "
"got an expired parent!"
);
return;
}
_dependent_canvases.insert(canvas);
_parent_canvases.insert(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeDependentCanvas(const CanvasWeakPtr& canvas)
void Canvas::addChildCanvas(const CanvasWeakPtr& canvas)
{
_dependent_canvases.erase(canvas);
if( canvas.expired() )
{
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Canvas::addChildCanvas(" << _node->getPath(true) << "): "
" got an expired child!"
);
return;
}
_child_canvases.insert(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeParentCanvas(const CanvasWeakPtr& canvas)
{
_parent_canvases.erase(canvas);
}
//----------------------------------------------------------------------------
void Canvas::removeChildCanvas(const CanvasWeakPtr& canvas)
{
_child_canvases.erase(canvas);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::createGroup(const std::string& name)
{
return boost::dynamic_pointer_cast<Group>
(
_root_group->createChild("group", name)
);
return _root_group->createChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getGroup(const std::string& name)
{
return _root_group->getChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getOrCreateGroup(const std::string& name)
{
return _root_group->getOrCreateChild<Group>(name);
}
//----------------------------------------------------------------------------
GroupPtr Canvas::getRootGroup()
{
return _root_group;
}
//----------------------------------------------------------------------------
void Canvas::setLayout(const LayoutRef& layout)
{
_layout = layout;
_layout->setCanvas(this);
_status |= LAYOUT_DIRTY;
}
//----------------------------------------------------------------------------
@@ -150,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);
@@ -168,24 +227,21 @@ namespace canvas
{
// Resizing causes a new texture to be created so we need to reapply all
// existing placements
for(size_t i = 0; i < _placements.size(); ++i)
{
if( !_placements[i].empty() )
_dirty_placements.push_back( _placements[i].front()->getProps() );
}
reloadPlacements();
}
osg::Camera* camera = _texture.getCamera();
// TODO Allow custom render order? For now just keep in order with
// property tree.
camera->setRenderOrder(osg::Camera::PRE_RENDER, _node->getIndex());
osg::Vec4 clear_color(0.0f, 0.0f, 0.0f , 1.0f);
parseColor(_node->getStringValue("background"), clear_color);
camera->setClearColor(clear_color);
camera->addChild(_root_group->getMatrixTransform());
// Ensure objects are drawn in order of traversal
camera->getOrCreateStateSet()->setBinName("TraversalOrderBin");
if( _texture.serviceable() )
{
setStatusFlags(STATUS_OK);
@@ -199,15 +255,36 @@ 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)
{
// TODO should we check if the image the child canvas is displayed
// within is really visible?
CanvasPtr canvas = canvas_weak.lock();
if( canvas )
canvas->_visible = true;
}
if( _render_dirty )
{
// Also mark all dependent (eg. recursively used) canvases as dirty
BOOST_FOREACH(CanvasWeakPtr canvas, _dependent_canvases)
// Also mark all canvases this canvas is displayed within as dirty
BOOST_FOREACH(CanvasWeakPtr canvas_weak, _parent_canvases)
{
if( !canvas.expired() )
canvas.lock()->_render_dirty = true;
CanvasPtr canvas = canvas_weak.lock();
if( canvas )
canvas->_render_dirty = true;
}
}
@@ -250,11 +327,7 @@ namespace canvas
if( placement_factory != _placement_factories.end() )
{
Placements& placements = _placements[ node->getIndex() ] =
placement_factory->second
(
node,
boost::static_pointer_cast<Canvas>(_self.lock())
);
placement_factory->second(node, this);
node->setStringValue
(
"status-msg",
@@ -267,12 +340,22 @@ namespace canvas
}
//----------------------------------------------------------------------------
naRef Canvas::addEventListener(const nasal::CallContext& ctx)
bool Canvas::addEventListener( const std::string& type,
const EventListener& cb )
{
if( !_root_group.get() )
naRuntimeError(ctx.c, "Canvas: No root group!");
throw std::runtime_error("Canvas::addEventListener: no root group!");
return _root_group->addEventListener(ctx);
return _root_group->addEventListener(type, cb);
}
//----------------------------------------------------------------------------
bool Canvas::dispatchEvent(const EventPtr& event)
{
if( !_root_group.get() )
throw std::runtime_error("Canvas::dispatchEvent: no root group!");
return _root_group->dispatchEvent(event);
}
//----------------------------------------------------------------------------
@@ -327,6 +410,7 @@ namespace canvas
if( _view_width == w )
return;
_view_width = w;
_status |= LAYOUT_DIRTY;
_texture.setViewSize(_view_width, _view_height);
}
@@ -337,6 +421,7 @@ namespace canvas
if( _view_height == h )
return;
_view_height = h;
_status |= LAYOUT_DIRTY;
_texture.setViewSize(_view_width, _view_height);
}
@@ -344,30 +429,43 @@ namespace canvas
//----------------------------------------------------------------------------
int Canvas::getViewWidth() const
{
return _view_width;
return _texture.getViewSize().x();
}
//----------------------------------------------------------------------------
int Canvas::getViewHeight() const
{
return _view_height;
return _texture.getViewSize().y();
}
//----------------------------------------------------------------------------
SGRect<int> Canvas::getViewport() const
{
return SGRect<int>(0, 0, getViewWidth(), getViewHeight());
}
//----------------------------------------------------------------------------
bool Canvas::handleMouseEvent(const MouseEventPtr& event)
{
if( !_root_group.get() )
if( !_root_group )
return false;
EventVisitor visitor( EventVisitor::TRAVERSE_DOWN,
event->getClientPos(),
event->getDelta() );
_root_group );
if( !_root_group->accept(visitor) )
return false;
return _event_manager->handleEvent(event, visitor.getPropagationPath());
}
//----------------------------------------------------------------------------
bool Canvas::propagateEvent( EventPtr const& event,
EventPropagationPath const& path )
{
return _event_manager->propagateEvent(event, path);
}
//----------------------------------------------------------------------------
void Canvas::childAdded( SGPropertyNode * parent,
SGPropertyNode * child )
@@ -399,8 +497,10 @@ namespace canvas
//----------------------------------------------------------------------------
void Canvas::valueChanged(SGPropertyNode* node)
{
if( boost::starts_with(node->getNameString(), "status")
|| node->getParent()->getNameString() == "bounding-box" )
const std::string& name = node->getNameString();
if( boost::starts_with(name, "status")
|| boost::starts_with(name, "data-") )
return;
_render_dirty = true;
@@ -408,21 +508,26 @@ namespace canvas
if( node->getParent()->getParent() == _node
&& node->getParent()->getNameString() == "placement" )
{
bool placement_dirty = false;
BOOST_FOREACH(Placements& placements, _placements)
size_t index = node->getIndex();
if( index < _placements.size() )
{
BOOST_FOREACH(PlacementPtr& placement, placements)
Placements& placements = _placements[index];
if( !placements.empty() )
{
// check if change can be directly handled by placement
if( placement->getProps() == node->getParent()
&& !placement->childChanged(node) )
placement_dirty = true;
bool placement_dirty = false;
BOOST_FOREACH(PlacementPtr& placement, placements)
{
// check if change can be directly handled by placement
if( placement->getProps() == node->getParent()
&& !placement->childChanged(node) )
placement_dirty = true;
}
if( !placement_dirty )
return;
}
}
if( !placement_dirty )
return;
// prevent double updates...
for( size_t i = 0; i < _dirty_placements.size(); ++i )
{
@@ -434,7 +539,7 @@ namespace canvas
}
else if( node->getParent() == _node )
{
if( node->getNameString() == "background" )
if( name == "background" )
{
osg::Vec4 color;
if( _texture.getCamera() && parseColor(node->getStringValue(), color) )
@@ -443,31 +548,41 @@ namespace canvas
_render_dirty = true;
}
}
else if( node->getNameString() == "mipmapping"
|| node->getNameString() == "coverage-samples"
|| node->getNameString() == "color-samples" )
else if( name == "mipmapping"
|| name == "coverage-samples"
|| name == "color-samples" )
{
_sampling_dirty = true;
}
else if( node->getNameString() == "render-always" )
else if( name == "additive-blend" )
{
_texture.useAdditiveBlend( node->getBoolValue() );
}
else if( name == "render-always" )
{
_render_always = node->getBoolValue();
}
else if( node->getNameString() == "size" )
else if( name == "size" )
{
if( node->getIndex() == 0 )
setSizeX( node->getIntValue() );
else if( node->getIndex() == 1 )
setSizeY( node->getIntValue() );
}
else if( node->getNameString() == "view" )
else if( name == "update" )
{
if( _root_group )
_root_group->update(0);
return update(0);
}
else if( name == "view" )
{
if( node->getIndex() == 0 )
setViewWidth( node->getIntValue() );
else if( node->getIndex() == 1 )
setViewHeight( node->getIntValue() );
}
else if( node->getNameString() == "freeze" )
else if( name == "freeze" )
_texture.setRender( node->getBoolValue() );
else
handled = false;
@@ -491,6 +606,24 @@ namespace canvas
return _cull_callback;
}
//----------------------------------------------------------------------------
void Canvas::reloadPlacements(const std::string& type)
{
for(size_t i = 0; i < _placements.size(); ++i)
{
if( _placements[i].empty() )
continue;
SGPropertyNode* child = _placements[i].front()->getProps();
if( type.empty()
// reload if type matches or no type specified
|| child->getStringValue("type", type.c_str()) == type )
{
_dirty_placements.push_back(child);
}
}
}
//----------------------------------------------------------------------------
void Canvas::addPlacementFactory( const std::string& type,
PlacementFactory factory )
@@ -500,27 +633,38 @@ namespace canvas
(
SG_GENERAL,
SG_WARN,
"Canvas::addPlacementFactory: replace existing factor for type " << type
"Canvas::addPlacementFactory: replace existing factory '" << type << "'"
);
_placement_factories[type] = factory;
}
//----------------------------------------------------------------------------
void Canvas::setSelf(const PropertyBasedElementPtr& self)
void Canvas::removePlacementFactory(const std::string& type)
{
PropertyBasedElement::setSelf(self);
PlacementFactoryMap::iterator it = _placement_factories.find(type);
if( it == _placement_factories.end() )
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Canvas::removePlacementFactory: no such factory '" << type << "'"
);
else
_placement_factories.erase(it);
}
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(self);
_root_group.reset( new Group(canvas, _node) );
_root_group->setSelf(_root_group);
//----------------------------------------------------------------------------
void Canvas::setSystemAdapter(const SystemAdapterPtr& system_adapter)
{
_system_adapter = system_adapter;
}
// Remove automatically created property listener as we forward them on our
// own
_root_group->removeListener();
_cull_callback = new CullCallback(canvas);
//----------------------------------------------------------------------------
SystemAdapterPtr Canvas::getSystemAdapter()
{
return _system_adapter;
}
//----------------------------------------------------------------------------
@@ -539,7 +683,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";
@@ -547,6 +691,7 @@ namespace canvas
//----------------------------------------------------------------------------
Canvas::PlacementFactoryMap Canvas::_placement_factories;
SystemAdapterPtr Canvas::_system_adapter;
} // namespace canvas
} // namespace simgear

View File

@@ -1,4 +1,4 @@
// The canvas for rendering with the 2d API
///@file The canvas for rendering with the 2d API
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
@@ -23,32 +23,40 @@
#include "ODGauge.hxx"
#include <simgear/canvas/elements/CanvasGroup.hxx>
#include <simgear/canvas/layout/Layout.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/nasal/cppbind/NasalObject.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/props/propertyObject.hxx>
#include <osg/NodeCallback>
#include <osg/observer_ptr>
#include <memory>
#include <boost/scoped_ptr.hpp>
#include <string>
namespace simgear
{
/// Canvas 2D drawing API
namespace canvas
{
class CanvasMgr;
class MouseEvent;
class Canvas:
public PropertyBasedElement
public PropertyBasedElement,
public nasal::Object
{
public:
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
};
@@ -71,29 +79,60 @@ namespace canvas
Canvas(SGPropertyNode* node);
virtual ~Canvas();
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
SystemAdapterPtr getSystemAdapter() const;
virtual void onDestroy();
void setCanvasMgr(CanvasMgr* canvas_mgr);
CanvasMgr* getCanvasMgr() const;
bool isInit() const;
/**
* Add a canvas which should be mared as dirty upon any change to this
* Add a canvas which should be marked as dirty upon any change to this
* canvas.
*
* This mechanism is used to eg. redraw a canvas if it's displaying
* another canvas (recursive canvases)
*/
void addDependentCanvas(const CanvasWeakPtr& canvas);
void addParentCanvas(const CanvasWeakPtr& canvas);
/**
* Add a canvas which should be marked visible if this canvas is visible.
*/
void addChildCanvas(const CanvasWeakPtr& canvas);
/**
* Stop notifying the given canvas upon changes
*/
void removeDependentCanvas(const CanvasWeakPtr& canvas);
void removeParentCanvas(const CanvasWeakPtr& canvas);
void removeChildCanvas(const CanvasWeakPtr& canvas);
/**
* Create a new group
*/
GroupPtr createGroup(const std::string& name = "");
/**
* Get an existing group with the given name
*/
GroupPtr getGroup(const std::string& name);
/**
* Get an existing group with the given name or otherwise create a new
* group
*/
GroupPtr getOrCreateGroup(const std::string& name);
/**
* Get the root group of the 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
*
@@ -104,7 +143,8 @@ namespace canvas
void update(double delta_time_sec);
naRef addEventListener(const nasal::CallContext& ctx);
bool addEventListener(const std::string& type, const EventListener& cb);
bool dispatchEvent(const EventPtr& event);
void setSizeX(int sx);
void setSizeY(int sy);
@@ -117,8 +157,11 @@ namespace canvas
int getViewWidth() const;
int getViewHeight() const;
SGRect<int> getViewport() const;
bool handleMouseEvent(const MouseEventPtr& event);
bool propagateEvent( EventPtr const& event,
EventPropagationPath const& path );
virtual void childAdded( SGPropertyNode * parent,
SGPropertyNode * child );
@@ -130,15 +173,26 @@ namespace canvas
CullCallbackPtr getCullCallback() const;
void reloadPlacements( const std::string& type = std::string() );
static void addPlacementFactory( const std::string& type,
PlacementFactory factory );
static void removePlacementFactory(const std::string& type);
/**
* Set global SystemAdapter for all Canvas/ODGauge instances.
*
* The SystemAdapter is responsible for application specific operations
* like loading images/fonts and adding/removing cameras to the scene
* graph.
*/
static void setSystemAdapter(const SystemAdapterPtr& system_adapter);
static SystemAdapterPtr getSystemAdapter();
protected:
SystemAdapterPtr _system_adapter;
CanvasMgr *_canvas_mgr;
std::auto_ptr<EventManager> _event_manager;
boost::scoped_ptr<EventManager> _event_manager;
int _size_x,
_size_y,
@@ -153,25 +207,28 @@ 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)
std::vector<SGPropertyNode*> _dirty_placements;
std::vector<Placements> _placements;
std::set<CanvasWeakPtr> _dependent_canvases; //<! Canvases which use this
// canvas and should be
// notified about changes
std::set<CanvasWeakPtr> _parent_canvases, //<! Canvases showing this canvas
_child_canvases; //<! Canvases displayed within
// this canvas
typedef std::map<std::string, PlacementFactory> PlacementFactoryMap;
static PlacementFactoryMap _placement_factories;
virtual void setSelf(const PropertyBasedElementPtr& self);
void setStatusFlags(unsigned int flags, bool set = true);
private:
static SystemAdapterPtr _system_adapter;
Canvas(const Canvas&); // = delete;
Canvas& operator=(const Canvas&); // = delete;
};

View File

@@ -26,6 +26,7 @@ namespace canvas
//----------------------------------------------------------------------------
Event::Event():
type(UNKNOWN),
time(-1),
propagation_stopped(false)
{
@@ -38,7 +39,13 @@ namespace canvas
}
//----------------------------------------------------------------------------
Event::Type Event::getType() const
bool Event::canBubble() const
{
return true;
}
//----------------------------------------------------------------------------
int Event::getType() const
{
return type;
}
@@ -46,14 +53,7 @@ namespace canvas
//----------------------------------------------------------------------------
std::string Event::getTypeString() const
{
switch( type )
{
# define ENUM_MAPPING(name, str) case name: return str;
# include "CanvasEventTypes.hxx"
# undef ENUM_MAPPING
default:
return "unknown";
}
return typeToStr(type);
}
//----------------------------------------------------------------------------
@@ -62,6 +62,12 @@ namespace canvas
return target;
}
//----------------------------------------------------------------------------
ElementWeakPtr Event::getCurrentTarget() const
{
return current_target;
}
//----------------------------------------------------------------------------
double Event::getTime() const
{
@@ -75,23 +81,57 @@ namespace canvas
}
//----------------------------------------------------------------------------
Event::Type Event::strToType(const std::string& str)
int Event::getOrRegisterType(const std::string& type_str)
{
int type = strToType(type_str);
if( type == UNKNOWN )
{
// Register new type
TypeMap& type_map = getTypeMap();
type = type_map.size() + 1; // ids start with 1 (after UNKNOWN)
type_map.insert(TypeMap::value_type(type_str, type));
}
return type;
}
//----------------------------------------------------------------------------
int Event::strToType(const std::string& str)
{
TypeMap const& type_map = getTypeMap();
TypeMap::map_by<name>::const_iterator it = type_map.by<name>().find(str);
if( it == type_map.by<name>().end() )
return UNKNOWN;
return it->second;
}
//----------------------------------------------------------------------------
std::string Event::typeToStr(int type)
{
TypeMap const& type_map = getTypeMap();
TypeMap::map_by<id>::const_iterator it = type_map.by<id>().find(type);
if( it == type_map.by<id>().end() )
return "unknown";
return it->second;
}
//----------------------------------------------------------------------------
Event::TypeMap& Event::getTypeMap()
{
typedef std::map<std::string, Type> TypeMap;
static TypeMap type_map;
if( type_map.empty() )
{
# define ENUM_MAPPING(type, str) type_map[ str ] = type;
# include "CanvasEventTypes.hxx"
# undef ENUM_MAPPING
# define ENUM_MAPPING(type, str)\
type_map.insert(TypeMap::value_type(str, type));
# include "CanvasEventTypes.hxx"
# undef ENUM_MAPPING
}
TypeMap::const_iterator it = type_map.find(str);
if( it == type_map.end() )
return UNKNOWN;
return it->second;
return type_map;
}
} // namespace canvas

View File

@@ -20,13 +20,15 @@
#define CANVAS_EVENT_HXX_
#include "canvas_fwd.hxx"
#include <boost/bimap.hpp>
namespace simgear
{
namespace canvas
{
class Event
class Event:
public SGReferenced
{
public:
@@ -36,12 +38,13 @@ namespace canvas
# define ENUM_MAPPING(name, str) name,
# include "CanvasEventTypes.hxx"
# undef ENUM_MAPPING
USER_TYPE ///<! first unused id to be used for user defined types (not
/// implemented yet)
CUSTOM_EVENT ///< all user defined event types share the same id. They
/// are just differentiated by using the type string.
};
Type type;
ElementWeakPtr target;
int type;
ElementWeakPtr target,
current_target;
double time;
bool propagation_stopped;
@@ -51,16 +54,41 @@ namespace canvas
// of the actual event instances.
virtual ~Event();
Type getType() const;
/**
* Get whether this events support bubbling
*/
virtual bool canBubble() const;
/**
* Set type of event.
*
* If no such type exists it is registered.
*/
void setType(const std::string& type);
int getType() const;
std::string getTypeString() const;
ElementWeakPtr getTarget() const;
ElementWeakPtr getCurrentTarget() const;
double getTime() const;
void stopPropagation();
static Type strToType(const std::string& str);
static int getOrRegisterType(const std::string& type);
static int strToType(const std::string& type);
static std::string typeToStr(int type);
protected:
struct name {};
struct id {};
typedef boost::bimaps::bimap<
boost::bimaps::tagged<std::string, name>,
boost::bimaps::tagged<int, id>
> TypeMap;
static TypeMap& getTypeMap();
};

View File

@@ -17,8 +17,9 @@
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "CanvasEventManager.hxx"
#include "MouseEvent.hxx"
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/canvas/elements/CanvasElement.hxx>
#include <cmath>
namespace simgear
{
@@ -60,6 +61,16 @@ namespace canvas
return !path.empty() && time > 0;
}
//----------------------------------------------------------------------------
void EventManager::MouseEventInfo::set( const MouseEventPtr& event,
const EventPropagationPath& p )
{
path = p;
time = event->time;
button = event->button;
pos = event->screen_pos;
}
//----------------------------------------------------------------------------
EventManager::EventManager():
_current_click_count(0)
@@ -71,39 +82,58 @@ namespace canvas
bool EventManager::handleEvent( const MouseEventPtr& event,
const EventPropagationPath& path )
{
bool handled = false;
switch( event->type )
{
case Event::MOUSE_DOWN:
_last_mouse_down = StampedPropagationPath(path, event->getTime());
_last_mouse_down.set(event, path);
break;
case Event::MOUSE_UP:
{
if( _last_mouse_down.path.empty() )
// Ignore mouse up without any previous mouse down
return false;
// If the mouse has moved while a button was down (aka. dragging) we
// need to notify the original element that the mouse has left it, and
// the new element that it has been entered
if( _last_mouse_down.path != path )
handled |= handleMove(event, path);
// normal mouseup
propagateEvent(event, path);
handled |= propagateEvent(event, path);
if( !_last_mouse_down.valid() )
// Ignore mouse up without any previous mouse down
return handled;
// now handle click/dblclick
if( checkClickDistance(path, _last_mouse_down.path) )
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
if( checkClickDistance(event->screen_pos, _last_mouse_down.pos) )
handled |=
handleClick(event, getCommonAncestor(_last_mouse_down.path, path));
_last_mouse_down.clear();
return true;
return handled;
}
case Event::DRAG:
if( !_last_mouse_down.valid() )
return false;
else
{
// OSG does not set button for drag events.
event->button = _last_mouse_down.button;
return propagateEvent(event, _last_mouse_down.path);
}
case Event::MOUSE_MOVE:
handleMove(event, path);
handled |= handleMove(event, path);
break;
case Event::MOUSE_LEAVE:
// Mouse leaves window and therefore also current mouseover element
handleMove(event, EventPropagationPath());
// Event is only send if mouse is moved outside the window or dragging
// has ended somewhere outside the window. In both cases a mouse button
// has been released, so no more mouse down or click...
_last_mouse_down.clear();
_last_click.clear();
return true;
case Event::WHEEL:
break;
@@ -111,11 +141,76 @@ namespace canvas
return false;
}
return propagateEvent(event, path);
return handled | propagateEvent(event, path);
}
//----------------------------------------------------------------------------
void EventManager::handleClick( const MouseEventPtr& event,
bool EventManager::propagateEvent( const EventPtr& event,
const EventPropagationPath& path )
{
event->target = path.back().element;
MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
// Event propagation similar to DOM Level 3 event flow:
// http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
// Position update only needed for drag event (as event needs to be
// delivered to element of initial mousedown, but with update positions)
if( mouse_event && mouse_event->type == MouseEvent::DRAG )
{
osg::Vec2f local_pos = mouse_event->client_pos;
// Capturing phase (currently just update position)
for( EventPropagationPath::const_iterator it = path.begin();
it != path.end();
++it )
{
ElementPtr el = it->element.lock();
if( !el )
continue;
it->local_pos = local_pos = el->posToLocal(local_pos);
}
}
bool const do_bubble = event->canBubble();
// Bubbling phase
for( EventPropagationPath::const_reverse_iterator
it = path.rbegin();
it != path.rend();
++it )
{
ElementPtr el = it->element.lock();
if( !el )
{
// Ignore element if it has been destroyed while traversing the event
// (eg. removed by another event handler)
if( do_bubble )
continue;
else
break;
}
// TODO provide functions to convert delta to local coordinates on demand.
// Maybe also provide a clone method for events as local coordinates
// might differ between different elements receiving the same event.
if( mouse_event )
mouse_event->local_pos = it->local_pos;
event->current_target = el;
el->handleEvent(event);
if( event->propagation_stopped || !do_bubble )
return true;
}
return true;
}
//----------------------------------------------------------------------------
bool EventManager::handleClick( const MouseEventPtr& event,
const EventPropagationPath& path )
{
MouseEventPtr click(new MouseEvent(*event));
@@ -130,8 +225,10 @@ namespace canvas
if( _current_click_count > 1 )
{
// Reset current click count if moved too far
if( !checkClickDistance(path, _last_click.path) )
// Reset current click count if moved too far or different button has
// been clicked
if( !checkClickDistance(event->screen_pos, _last_click.pos)
|| _last_click.button != event->button )
_current_click_count = 1;
}
}
@@ -145,104 +242,86 @@ namespace canvas
dbl_click->type = Event::DBL_CLICK;
}
propagateEvent(click, path);
bool handled = propagateEvent(click, path);
if( dbl_click )
propagateEvent(dbl_click, getCommonAncestor(_last_click.path, path));
handled |= propagateEvent( dbl_click,
getCommonAncestor(_last_click.path, path) );
_last_click = StampedPropagationPath(path, event->getTime());
_last_click.set(event, path);
return handled;
}
//----------------------------------------------------------------------------
void EventManager::handleMove( const MouseEventPtr& event,
bool EventManager::handleMove( const MouseEventPtr& event,
const EventPropagationPath& path )
{
if( _last_mouse_over.path == path )
return;
EventPropagationPath& last_path = _last_mouse_over.path;
if( last_path == path )
return false;
if( !_last_mouse_over.path.empty() )
bool handled = false;
// Leave old element
if( !last_path.empty() )
{
MouseEventPtr mouseout(new MouseEvent(*event));
mouseout->type = Event::MOUSE_OUT;
propagateEvent(mouseout, _last_mouse_over.path);
handled |= propagateEvent(mouseout, last_path);
// Send a mouseleave event to all ancestors of the currently left element
// which are not ancestor of the new element currently entered
EventPropagationPath path_leave = last_path;
for(size_t i = path_leave.size() - 1; i > 0; --i)
{
if( i < path.size() && path[i] == path_leave[i] )
break;
MouseEventPtr mouseleave(new MouseEvent(*event));
mouseleave->type = Event::MOUSE_LEAVE;
handled |= propagateEvent(mouseleave, path_leave);
path_leave.pop_back();
}
}
// Enter new element
if( !path.empty() )
{
MouseEventPtr mouseover(new MouseEvent(*event));
mouseover->type = Event::MOUSE_OVER;
propagateEvent(mouseover, path);
handled |= propagateEvent(mouseover, path);
// Send a mouseenter event to all ancestors of the currently entered
// element which are not ancestor of the old element currently being
// left
EventPropagationPath path_enter;
for(size_t i = 0; i < path.size(); ++i)
{
path_enter.push_back(path[i]);
if( i < last_path.size() && path[i] == last_path[i] )
continue;
MouseEventPtr mouseenter(new MouseEvent(*event));
mouseenter->type = Event::MOUSE_ENTER;
handled |= propagateEvent(mouseenter, path_enter);
}
}
_last_mouse_over.path = path;
}
//----------------------------------------------------------------------------
bool EventManager::propagateEvent( const EventPtr& event,
const EventPropagationPath& path )
{
event->target = path.back().element;
MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
// Event propagation similar to DOM Level 3 event flow:
// http://www.w3.org/TR/DOM-Level-3-Events/#event-flow
// Capturing phase
// for( EventTargets::iterator it = _target_path.begin();
// it != _target_path.end();
// ++it )
// {
// if( it->element )
// std::cout << it->element->getProps()->getPath() << " "
// << "(" << it->local_pos.x() << "|" << it->local_pos.y() << ")\n";
// }
// Bubbling phase
for( EventPropagationPath::const_reverse_iterator
it = path.rbegin();
it != path.rend();
++it )
{
ElementPtr el = it->element.lock();
if( !el )
// Ignore element if it has been destroyed while traversing the event
// (eg. removed by another event handler)
continue;
// TODO provide functions to convert position and delta to local
// coordinates on demand. Events shouldn't contain informations in
// local coordinates as they might differe between different elements
// receiving the same event.
// if( mouse_event && event->type != Event::DRAG )
// {
// // TODO transform pos and delta for drag events. Maybe we should just
// // store the global coordinates and convert to local coordinates
// // on demand.
//
// // Position and delta are specified in local coordinate system of
// // current element
// mouse_event->pos = it->local_pos;
// mouse_event->delta = it->local_delta;
// }
el->callListeners(event);
if( event->propagation_stopped )
return true;
}
return true;
return handled;
}
//----------------------------------------------------------------------------
bool
EventManager::checkClickDistance( const EventPropagationPath& path1,
const EventPropagationPath& path2 ) const
EventManager::checkClickDistance( const osg::Vec2f& pos1,
const osg::Vec2f& pos2 ) const
{
osg::Vec2 delta = path1.front().local_pos - path2.front().local_pos;
return delta.x() < drag_threshold
&& delta.y() < drag_threshold;
osg::Vec2 delta = pos1 - pos2;
return std::fabs(delta.x()) < drag_threshold
&& std::fabs(delta.y()) < drag_threshold;
}
//----------------------------------------------------------------------------
@@ -250,6 +329,9 @@ namespace canvas
EventManager::getCommonAncestor( const EventPropagationPath& path1,
const EventPropagationPath& path2 ) const
{
if( path1.empty() || path2.empty() )
return EventPropagationPath();
if( path1.back().element.lock() == path2.back().element.lock() )
return path2;

View File

@@ -29,11 +29,18 @@ namespace canvas
struct EventTarget
{
ElementWeakPtr element;
osg::Vec2f local_pos,
local_delta;
ElementWeakPtr element;
// Used as storage by EventManager during event propagation
mutable osg::Vec2f local_pos;
EventTarget( Element* el,
const osg::Vec2f pos = osg::Vec2f() ):
element(el),
local_pos(pos)
{}
};
typedef std::deque<EventTarget> EventPropagationPath;
inline bool operator==(const EventTarget& t1, const EventTarget& t2)
{
return t1.element.lock() == t2.element.lock();
@@ -47,6 +54,9 @@ namespace canvas
bool handleEvent( const MouseEventPtr& event,
const EventPropagationPath& path );
bool propagateEvent( const EventPtr& event,
const EventPropagationPath& path );
protected:
struct StampedPropagationPath
{
@@ -62,33 +72,39 @@ namespace canvas
// TODO if we really need the paths modify to not copy around the paths
// that much.
StampedPropagationPath _last_mouse_down,
_last_click,
_last_mouse_over;
StampedPropagationPath _last_mouse_over;
size_t _current_click_count;
struct MouseEventInfo:
public StampedPropagationPath
{
int button;
osg::Vec2f pos;
void set( const MouseEventPtr& event,
const EventPropagationPath& path );
} _last_mouse_down,
_last_click;
/**
* Propagate click event and handle multi-click (eg. create dblclick)
*/
void handleClick( const MouseEventPtr& event,
bool handleClick( const MouseEventPtr& event,
const EventPropagationPath& path );
/**
* Handle mouseover/enter/out/leave
*/
void handleMove( const MouseEventPtr& event,
bool handleMove( const MouseEventPtr& event,
const EventPropagationPath& path );
bool propagateEvent( const EventPtr& event,
const EventPropagationPath& path );
/**
* Check if two click events (either mousedown/up or two consecutive
* clicks) are inside a maximum distance to still create a click or
* dblclick event respectively.
*/
bool checkClickDistance( const EventPropagationPath& path1,
const EventPropagationPath& path2 ) const;
bool checkClickDistance( const osg::Vec2f& pos1,
const osg::Vec2f& pos2 ) const;
EventPropagationPath
getCommonAncestor( const EventPropagationPath& path1,
const EventPropagationPath& path2 ) const;

View File

@@ -20,7 +20,6 @@
#include "CanvasEvent.hxx"
#include "CanvasEventVisitor.hxx"
#include <simgear/canvas/elements/CanvasElement.hxx>
#include <iostream>
namespace simgear
{
@@ -30,14 +29,12 @@ namespace canvas
//----------------------------------------------------------------------------
EventVisitor::EventVisitor( TraverseMode mode,
const osg::Vec2f& pos,
const osg::Vec2f& delta ):
_traverse_mode( mode )
const ElementPtr& root ):
_traverse_mode( mode ),
_root(root)
{
if( mode == TRAVERSE_DOWN )
{
EventTarget target = {ElementWeakPtr(), pos, delta};
_target_path.push_back(target);
}
_target_path.push_back( EventTarget(NULL, pos) );
}
//----------------------------------------------------------------------------
@@ -61,32 +58,20 @@ namespace canvas
// We only need to check for hits while traversing down
if( _traverse_mode == TRAVERSE_DOWN )
{
// Transform event to local coordinates
const osg::Matrix& m = el.getMatrixTransform()->getInverseMatrix();
const osg::Vec2f& pos = _target_path.back().local_pos;
const osg::Vec2f local_pos
(
m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
);
const osg::Vec2f local_pos = el.posToLocal(pos);
// Don't check collision with root element (2nd element in _target_path)
// do event listeners attached to the canvas itself (its root group)
// always get called even if no element has been hit.
if( _target_path.size() > 2 && !el.hitBound(pos, local_pos) )
// Don't check specified root element for collision, as its purpose is to
// catch all events which have no target. This allows for example calling
// event listeners attached to the canvas itself (its root group) even if
// no element has been hit.
if( _root.get() != &el
&& !el.hitBound(_target_path.front().local_pos, pos, local_pos) )
return false;
const osg::Vec2f& delta = _target_path.back().local_delta;
const osg::Vec2f local_delta
(
m(0, 0) * delta[0] + m(1, 0) * delta[1],
m(0, 1) * delta[0] + m(1, 1) * delta[1]
);
_target_path.push_back( EventTarget(&el, local_pos) );
EventTarget target = {el.getWeakPtr(), local_pos, local_delta};
_target_path.push_back(target);
if( el.traverse(*this) || _target_path.size() <= 2 )
if( el.traverse(*this) || &el == _root.get() )
return true;
_target_path.pop_back();

View File

@@ -38,9 +38,15 @@ namespace canvas
TRAVERSE_DOWN
};
/**
*
* @param mode
* @param pos Mouse position
* @param root Element to dispatch events to if no element is hit
*/
EventVisitor( TraverseMode mode,
const osg::Vec2f& pos,
const osg::Vec2f& delta );
const ElementPtr& root = ElementPtr() );
virtual ~EventVisitor();
virtual bool traverse(Element& el);
virtual bool apply(Element& el);
@@ -51,6 +57,7 @@ namespace canvas
TraverseMode _traverse_mode;
EventPropagationPath _target_path;
ElementPtr _root;
};

View File

@@ -18,6 +18,7 @@
#include "CanvasMgr.hxx"
#include "Canvas.hxx"
#include "CanvasEventManager.hxx"
#include <boost/bind.hpp>
@@ -35,10 +36,8 @@ namespace canvas
}
//----------------------------------------------------------------------------
CanvasMgr::CanvasMgr( SGPropertyNode_ptr node,
SystemAdapterPtr system_adapter ):
PropertyBasedMgr(node, "texture", &canvasFactory),
_system_adapter(system_adapter)
CanvasMgr::CanvasMgr(SGPropertyNode_ptr node):
PropertyBasedMgr(node, "texture", &canvasFactory)
{
}
@@ -46,26 +45,25 @@ namespace canvas
//----------------------------------------------------------------------------
CanvasPtr CanvasMgr::createCanvas(const std::string& name)
{
return boost::static_pointer_cast<Canvas>( createElement(name) );
return static_cast<Canvas*>( createElement(name).get() );
}
//----------------------------------------------------------------------------
CanvasPtr CanvasMgr::getCanvas(size_t index) const
{
return boost::static_pointer_cast<Canvas>( getElement(index) );
return static_cast<Canvas*>( getElement(index).get() );
}
//----------------------------------------------------------------------------
CanvasPtr CanvasMgr::getCanvas(const std::string& name) const
{
return boost::static_pointer_cast<Canvas>( getElement(name) );
return static_cast<Canvas*>( getElement(name).get() );
}
//----------------------------------------------------------------------------
void CanvasMgr::elementCreated(PropertyBasedElementPtr element)
{
CanvasPtr canvas = boost::static_pointer_cast<Canvas>(element);
canvas->setSystemAdapter(_system_adapter);
CanvasPtr canvas = static_cast<Canvas*>(element.get());
canvas->setCanvasMgr(this);
}

View File

@@ -33,13 +33,9 @@ namespace canvas
public:
/**
* @param node Root node of branch used to control canvasses
* @param system_adapter Adapter for connecting between canvas and
* application framework
*
* @param node Root node of branch used to control canvasses
*/
CanvasMgr( SGPropertyNode_ptr node,
SystemAdapterPtr system_adapter );
CanvasMgr(SGPropertyNode_ptr node);
/**
* Create a new canvas
@@ -65,8 +61,6 @@ namespace canvas
protected:
SystemAdapterPtr _system_adapter;
virtual void elementCreated(PropertyBasedElementPtr element);
};

View File

@@ -0,0 +1,244 @@
// Canvas placement for placing a canvas texture onto osg objects.
//
// It also provides a SGPickCallback for passing mouse events to the canvas and
// manages emissive lighting of the placed canvas.
//
// Copyright (C) 2013 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 "Canvas.hxx"
#include "CanvasObjectPlacement.hxx"
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/props/props.hxx>
#include <simgear/scene/util/SGPickCallback.hxx>
#include <osgGA/GUIEventAdapter>
namespace simgear
{
namespace canvas
{
/**
* Handle picking events on object with a canvas placed onto
*/
class ObjectPickCallback:
public SGPickCallback
{
public:
ObjectPickCallback(const CanvasWeakPtr& canvas):
_canvas(canvas)
{}
virtual bool needsUV() const { return true; }
virtual bool buttonPressed( int,
const osgGA::GUIEventAdapter& ea,
const Info& info )
{
MouseEventPtr event(new MouseEvent(ea));
updatePosFromUV(event, info.uv);
if( ea.getEventType() == osgGA::GUIEventAdapter::SCROLL )
{
event->type = Event::WHEEL;
event->delta.set(0,0);
switch( ea.getScrollingMotion() )
{
case osgGA::GUIEventAdapter::SCROLL_UP:
event->delta.y() = 1;
break;
case osgGA::GUIEventAdapter::SCROLL_DOWN:
event->delta.y() = -1;
break;
default:
return false;
}
}
else
{
event->type = Event::MOUSE_DOWN;
}
return handleEvent(event);
}
virtual void buttonReleased( int,
const osgGA::GUIEventAdapter& ea,
const Info* info )
{
if( ea.getEventType() != osgGA::GUIEventAdapter::RELEASE )
return;
MouseEventPtr event(new MouseEvent(ea));
event->type = Event::MOUSE_UP;
updatePosFromUV(event, info ? info->uv : SGVec2d(-1,-1));
handleEvent(event);
}
virtual void mouseMoved( const osgGA::GUIEventAdapter& ea,
const Info* info )
{
// drag (currently only with LMB)
if( ea.getEventType() != osgGA::GUIEventAdapter::DRAG )
return;
MouseEventPtr event(new MouseEvent(ea));
event->type = Event::DRAG;
updatePosFromUV(event, info ? info->uv : SGVec2d(-1,-1));
handleEvent(event);
}
virtual bool hover( const osg::Vec2d& windowPos,
const Info& info )
{
// TODO somehow get more info about event (time, modifiers, pressed
// buttons, ...)
MouseEventPtr event(new MouseEvent);
event->type = Event::MOUSE_MOVE;
event->screen_pos = windowPos;
updatePosFromUV(event, info.uv);
return handleEvent(event);
}
virtual void mouseLeave( const osg::Vec2d& windowPos )
{
MouseEventPtr event(new MouseEvent);
event->type = Event::MOUSE_LEAVE;
event->screen_pos = windowPos;
handleEvent(event);
}
protected:
CanvasWeakPtr _canvas;
osg::Vec2f _last_pos,
_last_delta;
void updatePosFromUV(const MouseEventPtr& event, const SGVec2d& uv)
{
CanvasPtr canvas = _canvas.lock();
if( !canvas )
return;
osg::Vec2d pos( uv.x() * canvas->getViewWidth(),
(1 - uv.y()) * canvas->getViewHeight() );
_last_delta = pos - _last_pos;
_last_pos = pos;
event->client_pos = pos;
event->delta = _last_delta;
}
bool handleEvent(const MouseEventPtr& event)
{
CanvasPtr canvas = _canvas.lock();
if( !canvas )
return false;
return canvas->handleMouseEvent(event);
}
};
//----------------------------------------------------------------------------
ObjectPlacement::ObjectPlacement( SGPropertyNode* node,
const GroupPtr& group,
const CanvasWeakPtr& canvas ):
Placement(node),
_group(group),
_canvas(canvas)
{
// TODO make more generic and extendable for more properties
if( node->hasValue("emission") )
setEmission( node->getFloatValue("emission") );
if( node->hasValue("capture-events") )
setCaptureEvents( node->getBoolValue("capture-events") );
}
//----------------------------------------------------------------------------
ObjectPlacement::~ObjectPlacement()
{
assert( _group->getNumChildren() == 1 );
osg::Node *child = _group->getChild(0);
if( _group->getNumParents() )
{
osg::Group *parent = _group->getParent(0);
parent->addChild(child);
parent->removeChild(_group);
}
_group->removeChild(child);
}
//----------------------------------------------------------------------------
void ObjectPlacement::setEmission(float emit)
{
emit = SGMiscf::clip(emit, 0, 1);
if( !_material )
{
_material = new osg::Material;
_material->setColorMode(osg::Material::OFF);
_material->setDataVariance(osg::Object::DYNAMIC);
_group->getOrCreateStateSet()
->setAttribute(_material, ( osg::StateAttribute::ON
| osg::StateAttribute::OVERRIDE ) );
}
_material->setEmission(
osg::Material::FRONT_AND_BACK,
osg::Vec4(emit, emit, emit, emit)
);
}
//----------------------------------------------------------------------------
void ObjectPlacement::setCaptureEvents(bool enable)
{
if( !enable && _scene_user_data )
return;
if( enable && !_pick_cb )
_pick_cb = new ObjectPickCallback(_canvas);
_scene_user_data = SGSceneUserData::getOrCreateSceneUserData(_group);
_scene_user_data->setPickCallback(enable ? _pick_cb.get() : 0);
}
//----------------------------------------------------------------------------
bool ObjectPlacement::childChanged(SGPropertyNode* node)
{
if( node->getParent() != _node )
return false;
if( node->getNameString() == "emission" )
setEmission( node->getFloatValue() );
else if( node->getNameString() == "capture-events" )
setCaptureEvents( node->getBoolValue() );
else
return false;
return true;
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,78 @@
///@file Placement for putting a canvas texture onto OpenSceneGraph objects.
//
// It also provides a SGPickCallback for passing mouse events to the canvas and
// manages emissive lighting of the placed canvas.
//
// Copyright (C) 2013 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 CANVAS_OBJECT_PLACEMENT_HXX_
#define CANVAS_OBJECT_PLACEMENT_HXX_
#include "CanvasPlacement.hxx"
#include "canvas_fwd.hxx"
#include <simgear/scene/util/SGSceneUserData.hxx>
#include <osg/Material>
namespace simgear
{
namespace canvas
{
/**
* Place a Canvas onto an osg object (as texture).
*/
class ObjectPlacement:
public Placement
{
public:
typedef osg::ref_ptr<osg::Group> GroupPtr;
typedef osg::ref_ptr<osg::Material> MaterialPtr;
ObjectPlacement( SGPropertyNode* node,
const GroupPtr& group,
const CanvasWeakPtr& canvas );
virtual ~ObjectPlacement();
/**
* Set emissive lighting of the object the canvas is placed on.
*/
void setEmission(float emit);
/**
* Set whether pick events should be captured.
*/
void setCaptureEvents(bool enable);
virtual bool childChanged(SGPropertyNode* child);
protected:
typedef SGSharedPtr<SGPickCallback> PickCallbackPtr;
typedef osg::ref_ptr<SGSceneUserData> SGSceneUserDataPtr;
GroupPtr _group;
MaterialPtr _material;
CanvasWeakPtr _canvas;
PickCallbackPtr _pick_cb;
SGSceneUserDataPtr _scene_user_data;
};
} // namespace canvas
} // namespace simgear
#endif /* CANVAS_OBJECT_PLACEMENT_HXX_ */

View File

@@ -20,10 +20,12 @@
#define SG_CANVAS_SYSTEM_ADAPTER_HXX_
#include "canvas_fwd.hxx"
#include <simgear/nasal/nasal.h>
class SGSubsystem;
namespace simgear
{
namespace HTTP { class Client; }
namespace canvas
{
@@ -36,29 +38,8 @@ namespace canvas
virtual void addCamera(osg::Camera* camera) const = 0;
virtual void removeCamera(osg::Camera* camera) const = 0;
virtual osg::Image* getImage(const std::string& path) const = 0;
virtual naContext getNasalContext() const = 0;
/**
* Save passed reference to Nasal object from being deleted by the
* garbage collector.
*/
virtual int gcSave(naRef r) = 0;
/**
* Release an object previously passed to ::gcSave to allow it being
* cleaned up by the garbage collector.
*/
virtual void gcRelease(int key) = 0;
/**
* Call a Nasal function with the given environment and arguments.
*/
virtual naRef callMethod( naRef code,
naRef self,
int argc,
naRef* args,
naRef locals ) = 0;
virtual SGSubsystem* getSubsystem(const std::string& name) const = 0;
virtual HTTP::Client* getHTTPClient() const = 0;
};
} // namespace canvas

View File

@@ -0,0 +1,330 @@
// Window for placing a Canvas onto it (for dialogs, menus, etc.)
//
// Copyright (C) 2012 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 "CanvasMgr.hxx"
#include "CanvasSystemAdapter.hxx"
#include "CanvasWindow.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <osgGA/GUIEventHandler>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
const std::string Window::TYPE_NAME = "window";
//----------------------------------------------------------------------------
Window::Window( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
Element* parent ):
Image(canvas, node, parent_style, parent),
_attributes_dirty(0),
_resizable(false),
_capture_events(true),
_resize_top(node, "resize-top"),
_resize_right(node, "resize-right"),
_resize_bottom(node, "resize-bottom"),
_resize_left(node, "resize-left"),
_resize_status(node, "resize-status")
{
node->setFloatValue("source/right", 1);
node->setFloatValue("source/bottom", 1);
node->setBoolValue("source/normalized", true);
}
//----------------------------------------------------------------------------
Window::~Window()
{
if( _canvas_decoration )
_canvas_decoration->destroy();
}
//----------------------------------------------------------------------------
void Window::update(double delta_time_sec)
{
if( _attributes_dirty & DECORATION )
{
updateDecoration();
_attributes_dirty &= ~DECORATION;
}
Image::update(delta_time_sec);
}
//----------------------------------------------------------------------------
void Window::valueChanged(SGPropertyNode * node)
{
bool handled = false;
if( node->getParent() == _node )
{
handled = true;
const std::string& name = node->getNameString();
if( name == "resize" )
_resizable = node->getBoolValue();
else if( name == "update" )
update(0);
else if( name == "capture-events" )
_capture_events = node->getBoolValue();
else if( name == "decoration-border" )
parseDecorationBorder(node->getStringValue());
else if( boost::starts_with(name, "shadow-")
|| name == "content-size" )
_attributes_dirty |= DECORATION;
else
handled = false;
}
if( !handled )
Image::valueChanged(node);
}
//----------------------------------------------------------------------------
osg::Group* Window::getGroup()
{
return getMatrixTransform();
}
//----------------------------------------------------------------------------
const SGVec2<float> Window::getPosition() const
{
const osg::Matrix& m = getMatrixTransform()->getMatrix();
return SGVec2<float>( m(3, 0), m(3, 1) );
}
//----------------------------------------------------------------------------
const SGRect<float> Window::getScreenRegion() const
{
return getPosition() + getRegion();
}
//----------------------------------------------------------------------------
void Window::setCanvasContent(CanvasPtr canvas)
{
_canvas_content = canvas;
if( _layout )
{
canvas->setLayout(_layout);
_layout.clear();
}
if( _image_content )
// Placement within decoration canvas
_image_content->setSrcCanvas(canvas);
else
setSrcCanvas(canvas);
}
//----------------------------------------------------------------------------
CanvasWeakPtr Window::getCanvasContent() const
{
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
{
return _canvas_decoration;
}
//----------------------------------------------------------------------------
bool Window::isResizable() const
{
return _resizable;
}
//----------------------------------------------------------------------------
bool Window::isCapturingEvents() const
{
return _capture_events;
}
//----------------------------------------------------------------------------
void Window::raise()
{
// on writing the z-index the window always is moved to the top of all other
// windows with the same z-index.
set<int>("z-index", get<int>("z-index", 0));
}
//----------------------------------------------------------------------------
void Window::handleResize( uint8_t mode,
const osg::Vec2f& offset )
{
if( mode == NONE )
{
_resize_status = 0;
return;
}
else if( mode & INIT )
{
_resize_top = getRegion().t();
_resize_right = getRegion().r();
_resize_bottom = getRegion().b();
_resize_left = getRegion().l();
_resize_status = 1;
}
if( mode & BOTTOM )
_resize_bottom = getRegion().b() + offset.y();
else if( mode & TOP )
_resize_top = getRegion().t() + offset.y();
if( mode & canvas::Window::RIGHT )
_resize_right = getRegion().r() + offset.x();
else if( mode & canvas::Window::LEFT )
_resize_left = getRegion().l() + offset.x();
}
//----------------------------------------------------------------------------
void Window::parseDecorationBorder(const std::string& str)
{
_decoration_border = simgear::CSSBorder::parse(str);
_attributes_dirty |= DECORATION;
}
//----------------------------------------------------------------------------
void Window::updateDecoration()
{
int shadow_radius = get<float>("shadow-radius") + 0.5;
if( shadow_radius < 2 )
shadow_radius = 0;
CanvasPtr content = _canvas_content.lock();
SGRect<int> content_view
(
0,
0,
get<int>("content-size[0]", content ? content->getViewWidth() : 400),
get<int>("content-size[1]", content ? content->getViewHeight() : 300)
);
if( _decoration_border.isNone() && !shadow_radius )
{
setSrcCanvas(content);
set<int>("size[0]", content_view.width());
set<int>("size[1]", content_view.height());
_image_content.reset();
_image_shadow.reset();
if( _canvas_decoration )
_canvas_decoration->destroy();
_canvas_decoration.reset();
return;
}
if( !_canvas_decoration )
{
CanvasMgr* mgr = dynamic_cast<CanvasMgr*>(
Canvas::getSystemAdapter()->getSubsystem("Canvas")
);
if( !mgr )
{
SG_LOG(SG_GENERAL, SG_WARN, "canvas::Window: no canvas manager!");
return;
}
_canvas_decoration = mgr->createCanvas("window-decoration");
_canvas_decoration->getProps()
->setStringValue("background", "rgba(0,0,0,0)");
setSrcCanvas(_canvas_decoration);
_image_content = _canvas_decoration->getRootGroup()
->createChild<Image>("content");
_image_content->setSrcCanvas(content);
// Draw content on top of decoration
_image_content->set<int>("z-index", 1);
}
GroupPtr group_decoration =
_canvas_decoration->getOrCreateGroup("decoration");
group_decoration->set<int>("tf/t[0]", shadow_radius);
group_decoration->set<int>("tf/t[1]", shadow_radius);
// TODO do we need clipping or shall we trust the decorator not to draw over
// the shadow?
CSSBorder::Offsets const border =
_decoration_border.getAbsOffsets(content_view);
int shad2 = 2 * shadow_radius,
outer_width = border.l + content_view.width() + border.r + shad2,
outer_height = border.t + content_view.height() + border.b + shad2;
_canvas_decoration->setSizeX( outer_width );
_canvas_decoration->setSizeY( outer_height );
_canvas_decoration->setViewWidth( outer_width );
_canvas_decoration->setViewHeight( outer_height );
set<int>("size[0]", outer_width - shad2);
set<int>("size[1]", outer_height - shad2);
set<int>("outset", shadow_radius);
assert(_image_content);
_image_content->set<int>("x", shadow_radius + border.l);
_image_content->set<int>("y", shadow_radius + border.t);
_image_content->set<int>("size[0]", content_view.width());
_image_content->set<int>("size[1]", content_view.height());
if( !shadow_radius )
{
if( _image_shadow )
{
_image_shadow->destroy();
_image_shadow.reset();
}
return;
}
int shadow_inset = std::max<int>(get<float>("shadow-inset") + 0.5, 0),
slice_width = shadow_radius + shadow_inset;
_image_shadow = _canvas_decoration->getRootGroup()
->getOrCreateChild<Image>("shadow");
_image_shadow->set<std::string>("src", "gui/images/shadow.png");
_image_shadow->set<float>("slice", 7);
_image_shadow->set<std::string>("fill", "#000000");
_image_shadow->set<float>("slice-width", slice_width);
_image_shadow->set<int>("size[0]", outer_width);
_image_shadow->set<int>("size[1]", outer_height);
// Draw shadow below decoration
_image_shadow->set<int>("z-index", -1);
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,131 @@
// Window for placing a Canvas onto it (for dialogs, menus, etc.)
//
// Copyright (C) 2012 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 CANVAS_WINDOW_HXX_
#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>
#include <simgear/misc/CSSBorder.hxx>
#include <osg/Geode>
#include <osg/Geometry>
namespace simgear
{
namespace canvas
{
class Window:
public Image,
public LayoutItem
{
public:
static const std::string TYPE_NAME;
enum Resize
{
NONE = 0,
LEFT = 1,
RIGHT = LEFT << 1,
TOP = RIGHT << 1,
BOTTOM = TOP << 1,
INIT = BOTTOM << 1
};
/**
* @param node Property node containing settings for this window:
* capture-events Disable/Enable event capturing
* content-size[0-1] Size of content area (excluding
* decoration border)
* decoration-border Size of decoration border
* resize Enable resize cursor and properties
* shadow-inset Inset of shadow image
* shadow-radius Radius/outset of shadow image
*/
Window( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style = Style(),
Element* parent = 0 );
virtual ~Window();
virtual void update(double delta_time_sec);
virtual void valueChanged(SGPropertyNode* node);
osg::Group* getGroup();
const SGVec2<float> getPosition() const;
const SGRect<float> getScreenRegion() const;
void setCanvasContent(CanvasPtr canvas);
simgear::canvas::CanvasWeakPtr getCanvasContent() const;
void setLayout(const LayoutRef& layout);
CanvasPtr getCanvasDecoration() const;
bool isResizable() const;
bool isCapturingEvents() const;
/**
* Moves window on top of all other windows with the same z-index.
*
* @note If no z-index is set it defaults to 0.
*/
void raise();
void handleResize( uint8_t mode,
const osg::Vec2f& offset = osg::Vec2f() );
protected:
enum Attributes
{
DECORATION = 1
};
uint32_t _attributes_dirty;
CanvasPtr _canvas_decoration;
CanvasWeakPtr _canvas_content;
LayoutRef _layout;
ImagePtr _image_content,
_image_shadow;
bool _resizable,
_capture_events;
PropertyObject<int> _resize_top,
_resize_right,
_resize_bottom,
_resize_left,
_resize_status;
CSSBorder _decoration_border;
void parseDecorationBorder(const std::string& str);
void updateDecoration();
};
} // namespace canvas
} // namespace simgear
#endif /* CANVAS_WINDOW_HXX_ */

View File

@@ -29,6 +29,7 @@
#endif
#include "ODGauge.hxx"
#include "Canvas.hxx"
#include "CanvasSystemAdapter.hxx"
#include <simgear/debug/logstream.hxx>
@@ -43,6 +44,7 @@
#include <osg/ShadeModel>
#include <osg/StateSet>
#include <osg/FrameBufferObject> // for GL_DEPTH_STENCIL_EXT on Windows
#include <osgUtil/RenderBin>
#include <cassert>
@@ -51,18 +53,67 @@ namespace simgear
namespace canvas
{
class PreOrderBin:
public osgUtil::RenderBin
{
public:
PreOrderBin()
{}
PreOrderBin( const RenderBin& rhs,
const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ):
RenderBin(rhs, copyop)
{}
virtual osg::Object* cloneType() const
{
return new PreOrderBin();
}
virtual osg::Object* clone(const osg::CopyOp& copyop) const
{
return new PreOrderBin(*this,copyop);
}
virtual bool isSameKindAs(const osg::Object* obj) const
{
return dynamic_cast<const PreOrderBin*>(obj) != 0L;
}
virtual const char* className() const
{
return "PreOrderBin";
}
virtual void sort()
{
// Do not sort to keep traversal order...
}
};
#ifndef OSG_INIT_SINGLETON_PROXY
/**
* http://svn.openscenegraph.org/osg/OpenSceneGraph/trunk/include/osg/Object
*
* Helper macro that creates a static proxy object to call singleton function
* on it's construction, ensuring that the singleton gets initialized at
* startup.
*/
# define OSG_INIT_SINGLETON_PROXY(ProxyName, Func)\
static struct ProxyName{ ProxyName() { Func; } } s_##ProxyName;
#endif
OSG_INIT_SINGLETON_PROXY(
PreOrderBinProxy,
(osgUtil::RenderBin::addRenderBinPrototype("PreOrderBin", new PreOrderBin))
);
//----------------------------------------------------------------------------
ODGauge::ODGauge():
_size_x( -1 ),
_size_y( -1 ),
_view_width( -1 ),
_view_height( -1 ),
_use_image_coords( false ),
_use_stencil( false ),
_use_mipmapping( false ),
_flags(0),
_coverage_samples( 0 ),
_color_samples( 0 ),
rtAvailable( false )
_color_samples( 0 )
{
}
@@ -73,12 +124,6 @@ namespace canvas
clear();
}
//----------------------------------------------------------------------------
void ODGauge::setSystemAdapter(const SystemAdapterPtr& system_adapter)
{
_system_adapter = system_adapter;
}
//----------------------------------------------------------------------------
void ODGauge::setSize(int size_x, int size_y)
{
@@ -101,42 +146,43 @@ namespace canvas
updateCoordinateFrame();
}
//----------------------------------------------------------------------------
osg::Vec2s ODGauge::getViewSize() const
{
return osg::Vec2s(_view_width, _view_height);
}
//----------------------------------------------------------------------------
void ODGauge::useImageCoords(bool use)
{
if( use == _use_image_coords )
return;
_use_image_coords = use;
if( texture )
if( updateFlag(USE_IMAGE_COORDS, use) && texture )
updateCoordinateFrame();
}
//----------------------------------------------------------------------------
void ODGauge::useStencil(bool use)
{
if( use == _use_stencil )
return;
_use_stencil = use;
if( texture )
if( updateFlag(USE_STENCIL, use) && texture )
updateStencil();
}
//----------------------------------------------------------------------------
void ODGauge::setSampling( bool mipmapping,
int coverage_samples,
int color_samples )
void ODGauge::useAdditiveBlend(bool use)
{
if( _use_mipmapping == mipmapping
if( updateFlag(USE_ADDITIVE_BLEND, use) && camera )
updateBlendMode();
}
//----------------------------------------------------------------------------
void ODGauge::setSampling( bool mipmapping,
int coverage_samples,
int color_samples )
{
if( !updateFlag(USE_MIPMAPPING, mipmapping)
&& _coverage_samples == coverage_samples
&& _color_samples == color_samples )
return;
_use_mipmapping = mipmapping;
if( color_samples > coverage_samples )
{
SG_LOG
@@ -162,9 +208,9 @@ namespace canvas
}
//----------------------------------------------------------------------------
bool ODGauge::serviceable(void)
bool ODGauge::serviceable() const
{
return rtAvailable;
return _flags & AVAILABLE;
}
//----------------------------------------------------------------------------
@@ -196,26 +242,25 @@ namespace canvas
osg::PolygonMode::FILL ),
osg::StateAttribute::ON );
stateSet->setAttributeAndModes(
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.0f),
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.001f),
osg::StateAttribute::ON );
stateSet->setAttribute(new osg::ShadeModel(osg::ShadeModel::FLAT));
stateSet->setAttributeAndModes(
new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA),
osg::StateAttribute::ON );
if( !texture )
{
texture = new osg::Texture2D;
texture->setResizeNonPowerOfTwoHint(false);
texture->setTextureSize(_size_x, _size_y);
texture->setInternalFormat(GL_RGBA);
}
updateSampling();
updateBlendMode();
if( _system_adapter )
_system_adapter->addCamera(camera.get());
if( Canvas::getSystemAdapter() )
Canvas::getSystemAdapter()->addCamera(camera.get());
rtAvailable = true;
_flags |= AVAILABLE;
}
//----------------------------------------------------------------------------
@@ -229,12 +274,22 @@ namespace canvas
//----------------------------------------------------------------------------
void ODGauge::clear()
{
if( camera.valid() && _system_adapter )
_system_adapter->removeCamera(camera.get());
if( camera.valid() && Canvas::getSystemAdapter() )
Canvas::getSystemAdapter()->removeCamera(camera.get());
camera.release();
texture.release();
rtAvailable = false;
_flags &= ~AVAILABLE;
}
//----------------------------------------------------------------------------
bool ODGauge::updateFlag(Flags flag, bool enable)
{
if( bool(_flags & flag) == enable )
return false;
_flags ^= flag;
return true;
}
//----------------------------------------------------------------------------
@@ -249,7 +304,7 @@ namespace canvas
camera->setViewport(0, 0, _size_x, _size_y);
if( _use_image_coords )
if( _flags & USE_IMAGE_COORDS )
camera->setProjectionMatrix(
osg::Matrix::ortho2D(0, _view_width, _view_height, 0)
);
@@ -267,7 +322,7 @@ namespace canvas
GLbitfield mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
if( _use_stencil)
if( _flags & USE_STENCIL )
{
camera->attach( osg::Camera::PACKED_DEPTH_STENCIL_BUFFER,
GL_DEPTH_STENCIL_EXT );
@@ -289,18 +344,36 @@ namespace canvas
texture->setFilter(
osg::Texture2D::MIN_FILTER,
_use_mipmapping ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
: osg::Texture2D::LINEAR
(_flags & USE_MIPMAPPING) ? osg::Texture2D::LINEAR_MIPMAP_LINEAR
: osg::Texture2D::LINEAR
);
camera->attach(
osg::Camera::COLOR_BUFFER,
texture.get(),
0, 0,
_use_mipmapping,
_flags & USE_MIPMAPPING,
_coverage_samples,
_color_samples
);
}
//----------------------------------------------------------------------------
void ODGauge::updateBlendMode()
{
assert( camera );
camera->getOrCreateStateSet()
->setAttributeAndModes
(
(_flags & USE_ADDITIVE_BLEND)
? new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA,
osg::BlendFunc::ONE,
osg::BlendFunc::ONE )
: new osg::BlendFunc( osg::BlendFunc::SRC_ALPHA,
osg::BlendFunc::ONE_MINUS_SRC_ALPHA )
);
}
} // namespace canvas
} // namespace simgear

View File

@@ -53,8 +53,6 @@ namespace canvas
ODGauge();
virtual ~ODGauge();
void setSystemAdapter(const SystemAdapterPtr& system_adapter);
/**
* Set the size of the render target.
*
@@ -71,6 +69,8 @@ namespace canvas
*/
void setViewSize(int width, int height = -1);
osg::Vec2s getViewSize() const;
/**
* DEPRECATED
*
@@ -91,6 +91,12 @@ namespace canvas
*/
void useStencil(bool use = true);
/**
* Enable/Disable additive alpha blending (Can improve results with
* transparent background)
*/
void useAdditiveBlend(bool use = true);
/**
* Set sampling parameters for mipmapping and coverage sampling
* antialiasing.
@@ -112,7 +118,7 @@ namespace canvas
* Say if we can render to a texture.
* @return true if rtt is available
*/
bool serviceable(void);
bool serviceable() const;
/**
* Get the OSG camera for drawing this gauge.
@@ -128,27 +134,35 @@ namespace canvas
protected:
SystemAdapterPtr _system_adapter;
int _size_x,
_size_y,
_view_width,
_view_height;
bool _use_image_coords,
_use_stencil,
_use_mipmapping;
enum Flags
{
AVAILABLE = 1,
USE_IMAGE_COORDS = AVAILABLE << 1,
USE_STENCIL = USE_IMAGE_COORDS << 1,
USE_MIPMAPPING = USE_STENCIL << 1,
USE_ADDITIVE_BLEND = USE_MIPMAPPING << 1
};
uint32_t _flags;
// Multisampling parameters
int _coverage_samples,
_color_samples;
bool rtAvailable;
osg::ref_ptr<osg::Camera> camera;
osg::ref_ptr<osg::Texture2D> texture;
bool updateFlag(Flags flag, bool enable);
void updateCoordinateFrame();
void updateStencil();
void updateSampling();
void updateBlendMode();
};

View File

@@ -627,6 +627,7 @@ VG_API_CALL const VGubyte * vgGetString(VGStringID name);
#define OVG_SH_blend_dst_atop 1
VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height);
VG_API_CALL VGboolean vgHasContextSH();
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height);
VG_API_CALL void vgDestroyContextSH(void);

View File

@@ -38,4 +38,9 @@
#endif
// We currently do not support using images (inside paths). If we were going to
// use it loading and unloading needs to happen within OpenSceneGraph to handle
// synchronization correctly in multithreading mode.
#define SH_NO_IMAGE
#endif // __SHCONFIG_H

View File

@@ -53,7 +53,7 @@ VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,width,0,height);
glOrtho(0, width, 0, height, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();*/
@@ -61,6 +61,11 @@ VG_API_CALL VGboolean vgCreateContextSH(VGint width, VGint height)
return VG_TRUE;
}
VG_API_CALL VGboolean vgHasContextSH()
{
return g_context != NULL;
}
VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height)
{
VG_GETCONTEXT(VG_NO_RETVAL);
@@ -74,7 +79,7 @@ VG_API_CALL void vgResizeSurfaceSH(VGint width, VGint height)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,width,0,height);
glOrtho(0, width, 0, height, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

View File

@@ -34,7 +34,7 @@
#include <math.h>
#include <float.h>
#ifndef VG_API_MACOSX
#if !defined(VG_API_MACOSX) && !defined(__FreeBSD__)
# include <malloc.h>
#endif
@@ -158,18 +158,14 @@ SHfloat getMaxFloat();
#if defined(VG_API_LINUX)
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#elif defined(VG_API_MACOSX)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#elif defined(VG_API_WINDOWS)
#include <GL/gl.h>
#include <GL/glu.h>
#else
#define GL_GLEXT_LEGACY /* don't include glext.h */
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#endif

View File

@@ -37,6 +37,7 @@
#define _ARRAY_DEFINE
#include "shArrayBase.h"
#ifndef SH_NO_IMAGE
/*-----------------------------------------------------------
* Prepares the proper pixel pack/unpack info for the given
@@ -440,6 +441,7 @@ void shLoadColor(SHColor *c, const void *data, SHImageFormatDesc *f)
if (f->rmask == 0x0) { c->r = 1.0f; c->g = 1.0f; c->b = 1.0f; }
}
#endif // SH_NO_IMAGE
/*----------------------------------------------
* Color and Image constructors and destructors
@@ -461,18 +463,28 @@ void SHImage_ctor(SHImage *i)
i->data = NULL;
i->width = 0;
i->height = 0;
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
glGenTextures(1, &i->texture);
#endif
}
void SHImage_dtor(SHImage *i)
{
if (i->data != NULL)
free(i->data);
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
if (glIsTexture(i->texture))
glDeleteTextures(1, &i->texture);
#endif
}
#ifndef SH_NO_IMAGE
/*--------------------------------------------------------
* Finds appropriate OpenGL texture size for the size of
* the given image
@@ -553,6 +565,7 @@ void shUpdateImageTexture(SHImage *i, VGContext *c)
i->texwidth, i->texheight, 0,
i->fd.glformat, i->fd.gltype, i->data);
}
#endif // SH_NO_IMAGE
/*----------------------------------------------------------
* Creates a new image object and returns the handle to it
@@ -562,6 +575,10 @@ VG_API_CALL VGImage vgCreateImage(VGImageFormat format,
VGint width, VGint height,
VGbitfield allowedQuality)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
return VG_INVALID_HANDLE;
#else
SHImage *i = NULL;
SHImageFormatDesc fd;
VG_GETCONTEXT(VG_INVALID_HANDLE);
@@ -614,12 +631,16 @@ VG_API_CALL VGImage vgCreateImage(VGImageFormat format,
/* Add to resource list */
shImageArrayPushBack(&context->images, i);
VG_RETURN((VGImage)i);
#endif // SH_NO_IMAGE
}
VG_API_CALL void vgDestroyImage(VGImage image)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHint index;
VG_GETCONTEXT(VG_NO_RETVAL);
@@ -630,8 +651,9 @@ VG_API_CALL void vgDestroyImage(VGImage image)
/* Delete object and remove resource */
SH_DELETEOBJ(SHImage, (SHImage*)image);
shImageArrayRemoveAt(&context->images, index);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*---------------------------------------------------
@@ -642,6 +664,9 @@ VG_API_CALL void vgDestroyImage(VGImage image)
VG_API_CALL void vgClearImage(VGImage image,
VGint x, VGint y, VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *i;
SHColor clear;
SHuint8 *data;
@@ -682,9 +707,13 @@ VG_API_CALL void vgClearImage(VGImage image,
}}
shUpdateImageTexture(i, context);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
#ifndef SH_NO_IMAGE
/*------------------------------------------------------------
* Generic function for copying a rectangle area of pixels
* of size (width,height) among two data buffers. The size of
@@ -795,6 +824,8 @@ void shCopyPixels(SHuint8 *dst, VGImageFormat dstFormat, SHint dstStride,
}
}
#endif // SH_NO_IMAGE
/*---------------------------------------------------------
* Copies a rectangle area of pixels of size (width,height)
* from given data buffer to image surface at destination
@@ -806,6 +837,9 @@ VG_API_CALL void vgImageSubData(VGImage image,
VGImageFormat dataFormat,
VGint x, VGint y, VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *i;
VG_GETCONTEXT(VG_NO_RETVAL);
@@ -836,7 +870,9 @@ VG_API_CALL void vgImageSubData(VGImage image,
x, y, 0, 0, width, height);
shUpdateImageTexture(i, context);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*---------------------------------------------------------
@@ -851,6 +887,9 @@ VG_API_CALL void vgGetImageSubData(VGImage image,
VGint x, VGint y,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *i;
VG_GETCONTEXT(VG_NO_RETVAL);
@@ -878,9 +917,10 @@ VG_API_CALL void vgGetImageSubData(VGImage image,
shCopyPixels(data, dataFormat, dataStride,
i->data, i->fd.vgformat, i->texwidth * i->fd.bytes,
width, height, i->width, i->height,
0,0,x,x,width,height);
0,0,x,y,width,height);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*----------------------------------------------------------
@@ -894,6 +934,9 @@ VG_API_CALL void vgCopyImage(VGImage dst, VGint dx, VGint dy,
VGint width, VGint height,
VGboolean dither)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *s, *d;
SHuint8 *pixels;
@@ -933,7 +976,9 @@ VG_API_CALL void vgCopyImage(VGImage dst, VGint dx, VGint dy,
free(pixels);
shUpdateImageTexture(d, context);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*---------------------------------------------------------
@@ -946,6 +991,9 @@ VG_API_CALL void vgSetPixels(VGint dx, VGint dy,
VGImage src, VGint sx, VGint sy,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *i;
SHuint8 *pixels;
SHImageFormatDesc winfd;
@@ -986,6 +1034,7 @@ VG_API_CALL void vgSetPixels(VGint dx, VGint dy,
free(pixels);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*---------------------------------------------------------
@@ -999,6 +1048,9 @@ VG_API_CALL void vgWritePixels(const void * data, VGint dataStride,
VGint dx, VGint dy,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHuint8 *pixels;
SHImageFormatDesc winfd;
@@ -1043,7 +1095,8 @@ VG_API_CALL void vgWritePixels(const void * data, VGint dataStride,
free(pixels);
VG_RETURN(VG_NO_RETVAL);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*-----------------------------------------------------------
@@ -1056,6 +1109,9 @@ VG_API_CALL void vgGetPixels(VGImage dst, VGint dx, VGint dy,
VGint sx, VGint sy,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHImage *i;
SHuint8 *pixels;
SHImageFormatDesc winfd;
@@ -1093,7 +1149,9 @@ VG_API_CALL void vgGetPixels(VGImage dst, VGint dx, VGint dy,
free(pixels);
shUpdateImageTexture(i, context);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*-----------------------------------------------------------
@@ -1107,6 +1165,9 @@ VG_API_CALL void vgReadPixels(void * data, VGint dataStride,
VGint sx, VGint sy,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
SHuint8 *pixels;
SHImageFormatDesc winfd;
VG_GETCONTEXT(VG_NO_RETVAL);
@@ -1146,8 +1207,9 @@ VG_API_CALL void vgReadPixels(void * data, VGint dataStride,
0, 0, 0, 0, width, height);
free(pixels);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
/*----------------------------------------------------------
@@ -1160,6 +1222,9 @@ VG_API_CALL void vgCopyPixels(VGint dx, VGint dy,
VGint sx, VGint sy,
VGint width, VGint height)
{
#ifdef SH_NO_IMAGE
printf("ShivaVG: images not supported!");
#else
VG_GETCONTEXT(VG_NO_RETVAL);
VG_RETURN_ERR_IF(width <= 0 || height <= 0,
@@ -1170,8 +1235,9 @@ VG_API_CALL void vgCopyPixels(VGint dx, VGint dy,
glRasterPos2i(dx, dy);
glCopyPixels(sx, sy, width, height, GL_COLOR);
glRasterPos2i(0, 0);
VG_RETURN(VG_NO_RETVAL);
#endif // SH_NO_IMAGE
}
VG_API_CALL VGImage vgChildImage(VGImage parent,

View File

@@ -36,6 +36,9 @@
#define _ARRAY_DEFINE
#include "shArrayBase.h"
// We currently do not use gradients which need textures, so disable them to
// prevent freeing resources outside the correct OpenGL thread/context.
#define SH_NO_PAINT_TEXTURE
void SHPaint_ctor(SHPaint *p)
{
@@ -51,20 +54,26 @@ void SHPaint_ctor(SHPaint *p)
for (i=0; i<4; ++i) p->linearGradient[i] = 0.0f;
for (i=0; i<5; ++i) p->radialGradient[i] = 0.0f;
p->pattern = VG_INVALID_HANDLE;
#ifndef SH_NO_PAINT_TEXTURE
glGenTextures(1, &p->texture);
glBindTexture(GL_TEXTURE_1D, p->texture);
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, SH_GRADIENT_TEX_SIZE, 0,
GL_RGBA, GL_FLOAT, NULL);
#else
p->texture = 0;
#endif
}
void SHPaint_dtor(SHPaint *p)
{
SH_DEINITOBJ(SHStopArray, p->instops);
SH_DEINITOBJ(SHStopArray, p->stops);
#ifndef SH_NO_PAINT_TEXTURE
if (glIsTexture(p->texture))
glDeleteTextures(1, &p->texture);
#endif
}
VG_API_CALL VGPaint vgCreatePaint(void)
@@ -143,6 +152,7 @@ VG_API_CALL void vgPaintPattern(VGPaint paint, VGImage pattern)
void shUpdateColorRampTexture(SHPaint *p)
{
#ifndef SH_NO_PAINT_TEXTURE
SHint s=0;
SHStop *stop1, *stop2;
SHfloat rgba[SH_GRADIENT_TEX_COORDSIZE];
@@ -177,12 +187,15 @@ void shUpdateColorRampTexture(SHPaint *p)
CSTORE_RGBA1D_F(c, rgba, x);
}
}
/* Update texture image */
glBindTexture(GL_TEXTURE_1D, p->texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, SH_GRADIENT_TEX_SIZE,
GL_RGBA, GL_FLOAT, rgba);
#else
printf("ShivaVG: gradients not supported!");
#endif
}
void shValidateInputStops(SHPaint *p)
@@ -344,6 +357,7 @@ void shGenerateStops(SHPaint *p, SHfloat minOffset, SHfloat maxOffset,
void shSetGradientTexGLState(SHPaint *p)
{
#ifndef SH_NO_PAINT_TEXTURE
glBindTexture(GL_TEXTURE_1D, p->texture);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -359,6 +373,9 @@ void shSetGradientTexGLState(SHPaint *p)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1,1,1,1);
#else
printf("ShivaVG: gradients not supported!");
#endif
}
void shSetPatternTexGLState(SHPaint *p, VGContext *c)

View File

@@ -314,9 +314,9 @@ VG_API_CALL void vgDrawPath(VGPath path, VGbitfield paintModes)
}
/* TODO: Turn antialiasing on/off */
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
glEnable(GL_MULTISAMPLE);
// glDisable(GL_LINE_SMOOTH);
// glDisable(GL_POLYGON_SMOOTH);
// glEnable(GL_MULTISAMPLE);
/* Pick paint if available or default*/
fill = (context->fillPaint ? context->fillPaint : &context->defaultPaint);
@@ -364,26 +364,27 @@ VG_API_CALL void vgDrawPath(VGPath path, VGbitfield paintModes)
/* TODO: Is there any way to do this safely along
with the paint generation pass?? */
glDisable(GL_BLEND);
glDisable(GL_MULTISAMPLE);
// glDisable(GL_MULTISAMPLE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
shDrawBoundBox(context, p, VG_FILL_PATH);
/* Reset state */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
// glDisable(GL_BLEND);
}
/* TODO: Turn antialiasing on/off */
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
glEnable(GL_MULTISAMPLE);
// glDisable(GL_LINE_SMOOTH);
// glDisable(GL_POLYGON_SMOOTH);
// glEnable(GL_MULTISAMPLE);
if ((paintModes & VG_STROKE_PATH) &&
context->strokeLineWidth > 0.0f) {
if (1) {/*context->strokeLineWidth > 1.0f) {*/
#if 0
if (1) {/*context->strokeLineWidth > 1.0f) {*/
#endif
if (shIsStrokeCacheValid( context, p ) == VG_FALSE)
{
/* Generate stroke triangles in user space */
@@ -411,15 +412,15 @@ VG_API_CALL void vgDrawPath(VGPath path, VGbitfield paintModes)
/* Clear stencil for sure */
glDisable(GL_BLEND);
glDisable(GL_MULTISAMPLE);
// glDisable(GL_MULTISAMPLE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
shDrawBoundBox(context, p, VG_STROKE_PATH);
/* Reset state */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
// glDisable(GL_BLEND);
#if 0
}else{
/* Simulate thin stroke by alpha */
@@ -438,6 +439,7 @@ VG_API_CALL void vgDrawPath(VGPath path, VGbitfield paintModes)
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
#endif
}

View File

@@ -124,11 +124,13 @@ int shLineLineXsection(SHVector2 *o1, SHVector2 *v1,
SHfloat DX = rightU * (-v2->y) - rightD * (-v2->x);
/*SHfloat DY = v1.x * rightD - v1.y * rightU;*/
SHfloat t1 = DX / D;
SHfloat t1;
if (D == 0.0f)
return 0;
t1 = DX / D;
xsection->x = o1->x + t1*v1->x;
xsection->y = o1->y + t1*v1->y;
return 1;

View File

@@ -39,8 +39,8 @@ namespace canvas
#define SG_FWD_DECL(name)\
class name;\
typedef boost::shared_ptr<name> name##Ptr;\
typedef boost::weak_ptr<name> name##WeakPtr;
typedef SGSharedPtr<name> name##Ptr;\
typedef SGWeakPtr<name> name##WeakPtr;
SG_FWD_DECL(Canvas)
SG_FWD_DECL(Element)
@@ -49,10 +49,19 @@ namespace canvas
SG_FWD_DECL(Map)
SG_FWD_DECL(Path)
SG_FWD_DECL(Text)
SG_FWD_DECL(Window)
SG_FWD_DECL(Event)
SG_FWD_DECL(EventListener)
SG_FWD_DECL(CustomEvent)
SG_FWD_DECL(MouseEvent)
#undef SG_FWD_DECL
#define SG_FWD_DECL(name)\
class name;\
typedef boost::shared_ptr<name> name##Ptr;\
typedef boost::weak_ptr<name> name##WeakPtr;
SG_FWD_DECL(Placement)
SG_FWD_DECL(SystemAdapter)
@@ -61,6 +70,9 @@ namespace canvas
class EventManager;
class EventVisitor;
struct EventTarget;
typedef std::deque<EventTarget> EventPropagationPath;
typedef std::map<std::string, const SGPropertyNode*> Style;
typedef ElementPtr (*ElementFactory)( const CanvasWeakPtr&,
const SGPropertyNode_ptr&,
@@ -73,6 +85,8 @@ namespace canvas
typedef boost::function<Placements( SGPropertyNode*,
CanvasPtr )> PlacementFactory;
typedef boost::function<void(const EventPtr&)> EventListener;
} // namespace canvas
} // namespace simgear

View File

@@ -6,7 +6,11 @@ set(HEADERS
CanvasImage.hxx
CanvasMap.hxx
CanvasPath.hxx
CanvasText.hxx
CanvasText.hxx
)
set(DETAIL_HEADERS
detail/add_segment_variadic.hxx
)
set(SOURCES
@@ -18,4 +22,10 @@ set(SOURCES
CanvasText.cxx
)
simgear_scene_component(canvas-elements canvas/elements "${SOURCES}" "${HEADERS}")
simgear_scene_component(canvas-elements canvas/elements "${SOURCES}" "${HEADERS}")
simgear_component(canvas-elements/detail canvas/elements/detail "" "${DETAIL_HEADERS}")
add_boost_test(canvas_element
SOURCES canvas_element_test.cpp
LIBRARIES ${TEST_LIBS}
)

View File

@@ -18,9 +18,11 @@
#include "CanvasElement.hxx"
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasEventListener.hxx>
#include <simgear/canvas/CanvasEventVisitor.hxx>
#include <simgear/canvas/MouseEvent.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/math/SGMisc.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/material/parseBlendFunc.hxx>
#include <osg/Drawable>
#include <osg/Geode>
@@ -28,11 +30,10 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <boost/tokenizer.hpp>
#include <cassert>
#include <cmath>
#include <cstring>
namespace simgear
@@ -41,128 +42,242 @@ namespace canvas
{
const std::string NAME_TRANSFORM = "tf";
//----------------------------------------------------------------------------
void Element::removeListener()
/**
* glScissor with coordinates relative to different reference frames.
*/
class Element::RelativeScissor:
public osg::Scissor
{
_node->removeChangeListener(this);
public:
ReferenceFrame _coord_reference;
osg::observer_ptr<osg::Node> _node;
RelativeScissor(osg::Node* node = NULL):
_coord_reference(GLOBAL),
_node(node)
{
_width = 0;
_height = 0;
}
/** Copy constructor using CopyOp to manage deep vs shallow copy. */
RelativeScissor( const RelativeScissor& vp,
const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY ):
Scissor(vp, copyop),
_coord_reference(vp._coord_reference),
_node(vp._node)
{}
META_StateAttribute(simgear, RelativeScissor, SCISSOR);
/** Return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs. */
virtual int compare(const StateAttribute& sa) const
{
// check the types are equal and then create the rhs variable
// used by the COMPARE_StateAttribute_Parameter macros below.
COMPARE_StateAttribute_Types(RelativeScissor,sa)
// compare each parameter in turn against the rhs.
COMPARE_StateAttribute_Parameter(_x)
COMPARE_StateAttribute_Parameter(_y)
COMPARE_StateAttribute_Parameter(_width)
COMPARE_StateAttribute_Parameter(_height)
COMPARE_StateAttribute_Parameter(_coord_reference)
COMPARE_StateAttribute_Parameter(_node)
return 0; // passed all the above comparison macros, must be equal.
}
virtual void apply(osg::State& state) const
{
if( _width <= 0 || _height <= 0 )
return;
const osg::Viewport* vp = state.getCurrentViewport();
float w2 = 0.5 * vp->width(),
h2 = 0.5 * vp->height();
osg::Matrix model_view
(
w2, 0, 0, 0,
0, h2, 0, 0,
0, 0, 1, 0,
w2, h2, 0, 1
);
model_view.preMult(state.getProjectionMatrix());
if( _coord_reference != GLOBAL )
{
osg::Node* ref_obj = _node.get();
if( _coord_reference == PARENT )
{
if( _node->getNumParents() < 1 )
{
SG_LOG(SG_GL, SG_WARN, "RelativeScissor: missing parent.");
return;
}
ref_obj = _node->getParent(0);
}
osg::MatrixList const& parent_matrices = ref_obj->getWorldMatrices();
assert( !parent_matrices.empty() );
model_view.preMult(parent_matrices.front());
}
const osg::Vec2 scale( model_view(0,0), model_view(1,1)),
offset(model_view(3,0), model_view(3,1));
// TODO check/warn for rotation?
GLint x = SGMiscf::roundToInt(scale.x() * _x + offset.x()),
y = SGMiscf::roundToInt(scale.y() * _y + offset.y()),
w = SGMiscf::roundToInt(std::fabs(scale.x()) * _width),
h = SGMiscf::roundToInt(std::fabs(scale.y()) * _height);
if( scale.x() < 0 )
x -= w;
if( scale.y() < 0 )
y -= h;
glScissor(x, y, w, h);
}
bool contains(const osg::Vec2f& pos) const
{
return _x <= pos.x() && pos.x() <= _x + _width
&& _y <= pos.y() && pos.y() <= _y + _height;
}
bool contains( const osg::Vec2f& global_pos,
const osg::Vec2f& parent_pos,
const osg::Vec2f& local_pos ) const
{
switch( _coord_reference )
{
case GLOBAL: return contains(global_pos);
case PARENT: return contains(parent_pos);
case LOCAL: return contains(local_pos);
}
return false;
}
};
//----------------------------------------------------------------------------
Element::OSGUserData::OSGUserData(ElementPtr element):
element(element)
{
}
//----------------------------------------------------------------------------
Element::~Element()
{
removeListener();
if( !_transform.valid() )
return;
BOOST_FOREACH(osg::Group* parent, _transform->getParents())
for(unsigned int i = 0; i < _transform->getNumChildren(); ++i)
{
parent->removeChild(_transform);
OSGUserData* ud =
static_cast<OSGUserData*>(_transform->getChild(i)->getUserData());
if( ud )
// Ensure parent is cleared to prevent accessing released memory if an
// element somehow survives longer than his parent.
ud->element->_parent = 0;
}
}
//----------------------------------------------------------------------------
ElementWeakPtr Element::getWeakPtr() const
void Element::onDestroy()
{
return boost::static_pointer_cast<Element>(_self.lock());
if( !_transform.valid() )
return;
// The transform node keeps a reference on this element, so ensure it is
// deleted.
BOOST_FOREACH(osg::Group* parent, _transform->getParents())
{
parent->removeChild(_transform.get());
}
// Hide in case someone still holds a reference
setVisible(false);
removeListener();
_parent = 0;
_transform = 0;
}
//----------------------------------------------------------------------------
ElementPtr Element::getParent() const
{
return _parent;
}
//----------------------------------------------------------------------------
CanvasWeakPtr Element::getCanvas() const
{
return _canvas;
}
//----------------------------------------------------------------------------
void Element::update(double dt)
{
if( !_transform->getNodeMask() )
// Don't do anything if element is hidden
if( !isVisible() )
return;
if( _transform_dirty )
// Trigger matrix update
getMatrix();
// Update bounding box on manual update (manual updates pass zero dt)
if( dt == 0 && _drawable )
_drawable->getBound();
if( (_attributes_dirty & BLEND_FUNC) && _transform.valid() )
{
osg::Matrix m;
for( size_t i = 0; i < _transform_types.size(); ++i )
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_transform->setMatrix(m);
_transform_dirty = false;
parseBlendFunc(
_transform->getOrCreateStateSet(),
_node->getChild("blend-source"),
_node->getChild("blend-destination"),
_node->getChild("blend-source-rgb"),
_node->getChild("blend-destination-rgb"),
_node->getChild("blend-source-alpha"),
_node->getChild("blend-destination-alpha")
);
_attributes_dirty &= ~BLEND_FUNC;
}
}
//----------------------------------------------------------------------------
naRef Element::addEventListener(const nasal::CallContext& ctx)
bool Element::addEventListener( const std::string& type_str,
const EventListener& cb )
{
const std::string type_str = ctx.requireArg<std::string>(0);
naRef code = ctx.requireArg<naRef>(1);
SG_LOG
(
SG_NASAL,
SG_GENERAL,
SG_INFO,
"addEventListener(" << _node->getPath() << ", " << type_str << ")"
);
Event::Type type = Event::strToType(type_str);
if( type == Event::UNKNOWN )
naRuntimeError( ctx.c,
"addEventListener: Unknown event type %s",
type_str.c_str() );
_listener[ Event::getOrRegisterType(type_str) ].push_back(cb);
return true;
}
_listener[ type ].push_back
(
boost::make_shared<EventListener>( code,
_canvas.lock()->getSystemAdapter() )
);
return naNil();
//----------------------------------------------------------------------------
void Element::clearEventListener()
{
_listener.clear();
}
//----------------------------------------------------------------------------
bool Element::accept(EventVisitor& visitor)
{
if( !isVisible() )
return false;
return visitor.apply(*this);
}
@@ -181,39 +296,104 @@ namespace canvas
}
//----------------------------------------------------------------------------
void Element::callListeners(const canvas::EventPtr& event)
bool Element::handleEvent(const EventPtr& event)
{
ListenerMap::iterator listeners = _listener.find(event->getType());
if( listeners == _listener.end() )
return;
return false;
BOOST_FOREACH(EventListenerPtr listener, listeners->second)
listener->call(event);
}
//----------------------------------------------------------------------------
bool Element::hitBound( const osg::Vec2f& pos,
const osg::Vec2f& local_pos ) const
{
const osg::Vec3f pos3(pos, 0);
// Drawables have a bounding box...
if( _drawable )
{
if( !_drawable->getBound().contains(osg::Vec3f(local_pos, 0)) )
return false;
}
// ... for other elements, i.e. groups only a bounding sphere is available
else if( !_transform->getBound().contains(osg::Vec3f(pos, 0)) )
return false;
BOOST_FOREACH(EventListener const& listener, listeners->second)
try
{
listener(event);
}
catch( std::exception const& ex )
{
SG_LOG(
SG_GENERAL,
SG_WARN,
"canvas::Element: event handler error: '" << ex.what() << "'"
);
}
return true;
}
//----------------------------------------------------------------------------
osg::ref_ptr<osg::MatrixTransform> Element::getMatrixTransform()
bool Element::dispatchEvent(const EventPtr& event)
{
return _transform;
EventPropagationPath path;
path.push_back( EventTarget(this) );
for( Element* parent = _parent;
parent != NULL;
parent = parent->_parent )
path.push_front( EventTarget(parent) );
CanvasPtr canvas = _canvas.lock();
if( !canvas )
return false;
return canvas->propagateEvent(event, path);
}
//----------------------------------------------------------------------------
bool Element::hitBound( const osg::Vec2f& global_pos,
const osg::Vec2f& parent_pos,
const osg::Vec2f& local_pos ) const
{
if( _scissor && !_scissor->contains(global_pos, parent_pos, local_pos) )
return false;
const osg::Vec3f pos3(parent_pos, 0);
// Drawables have a bounding box...
if( _drawable )
return _drawable->getBound().contains(osg::Vec3f(local_pos, 0));
else if( _transform.valid() )
// ... for other elements, i.e. groups only a bounding sphere is available
return _transform->getBound().contains(osg::Vec3f(parent_pos, 0));
else
return false;
}
//----------------------------------------------------------------------------
void Element::setVisible(bool visible)
{
if( _transform.valid() )
// TODO check if we need another nodemask
_transform->setNodeMask(visible ? 0xffffffff : 0);
}
//----------------------------------------------------------------------------
bool Element::isVisible() const
{
return _transform.valid() && _transform->getNodeMask() != 0;
}
//----------------------------------------------------------------------------
osg::MatrixTransform* Element::getMatrixTransform()
{
return _transform.get();
}
//----------------------------------------------------------------------------
osg::MatrixTransform const* Element::getMatrixTransform() const
{
return _transform.get();
}
//----------------------------------------------------------------------------
osg::Vec2f Element::posToLocal(const osg::Vec2f& pos) const
{
getMatrix();
const osg::Matrix& m = _transform->getInverseMatrix();
return osg::Vec2f
(
m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1)
);
}
//----------------------------------------------------------------------------
@@ -226,7 +406,7 @@ namespace canvas
_transform_types.resize( child->getIndex() + 1 );
_transform_types[ child->getIndex() ] = TT_NONE;
_transform_dirty = true;
_attributes_dirty |= TRANSFORM;
return;
}
else if( parent->getParent() == _node
@@ -247,7 +427,7 @@ namespace canvas
else if( name == "s" )
type = TT_SCALE;
_transform_dirty = true;
_attributes_dirty |= TRANSFORM;
return;
}
@@ -257,26 +437,37 @@ namespace canvas
//----------------------------------------------------------------------------
void Element::childRemoved(SGPropertyNode* parent, SGPropertyNode* child)
{
if( parent == _node && child->getNameString() == NAME_TRANSFORM )
if( parent == _node )
{
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
if( child->getNameString() == NAME_TRANSFORM )
{
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Element::childRemoved: unknown transform: " << child->getPath()
);
if( !_transform.valid() )
return;
if( child->getIndex() >= static_cast<int>(_transform_types.size()) )
{
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Element::childRemoved: unknown transform: " << child->getPath()
);
return;
}
_transform_types[ child->getIndex() ] = TT_NONE;
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
_transform_types.pop_back();
_attributes_dirty |= TRANSFORM;
return;
}
_transform_types[ child->getIndex() ] = TT_NONE;
while( !_transform_types.empty() && _transform_types.back() == TT_NONE )
_transform_types.pop_back();
_transform_dirty = true;
return;
else if( StyleInfo const* style = getStyleInfo(child->getNameString()) )
{
if( setStyle(getParentStyle(child), style) )
return;
}
}
childRemoved(child);
@@ -288,18 +479,30 @@ namespace canvas
SGPropertyNode *parent = child->getParent();
if( parent == _node )
{
if( setStyle(child) )
const std::string& name = child->getNameString();
if( boost::starts_with(name, "data-") )
return;
else if( child->getNameString() == "update" )
else if( StyleInfo const* style_info = getStyleInfo(name) )
{
SGPropertyNode const* style = child;
if( isStyleEmpty(child) )
{
child->clearValue();
style = getParentStyle(child);
}
setStyle(style, style_info);
return;
}
else if( name == "update" )
return update(0);
else if( child->getNameString() == "visible" )
// TODO check if we need another nodemask
return _transform->setNodeMask( child->getBoolValue() ? 0xffffffff : 0 );
else if( boost::starts_with(name, "blend-") )
return (void)(_attributes_dirty |= BLEND_FUNC);
}
else if( parent->getParent() == _node
else if( parent
&& parent->getParent() == _node
&& parent->getNameString() == NAME_TRANSFORM )
{
_transform_dirty = true;
_attributes_dirty |= TRANSFORM;
return;
}
@@ -307,23 +510,23 @@ namespace canvas
}
//----------------------------------------------------------------------------
bool Element::setStyle(const SGPropertyNode* child)
bool Element::setStyle( const SGPropertyNode* child,
const StyleInfo* style_info )
{
StyleSetters::const_iterator setter =
_style_setters.find(child->getNameString());
if( setter == _style_setters.end() )
return false;
setter->second(child);
return true;
return canApplyStyle(child) && setStyleImpl(child, style_info);
}
//----------------------------------------------------------------------------
void Element::setClip(const std::string& clip)
{
osg::StateSet* ss = getOrCreateStateSet();
if( !ss )
return;
if( clip.empty() || clip == "auto" )
{
getOrCreateStateSet()->removeAttribute(osg::StateAttribute::SCISSOR);
ss->removeAttribute(osg::StateAttribute::SCISSOR);
_scissor = 0;
return;
}
@@ -336,17 +539,22 @@ namespace canvas
return;
}
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
const boost::char_separator<char> del(", \t\npx");
tokenizer tokens(clip.begin() + RECT.size(), clip.end() - 1, del);
const std::string sep(", \t\npx");
int comp = 0;
int values[4];
for( tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && comp < 4;
++tok, ++comp )
float values[4];
for(size_t pos = RECT.size(); comp < 4; ++comp)
{
values[comp] = boost::lexical_cast<int>(*tok);
pos = clip.find_first_not_of(sep, pos);
if( pos == std::string::npos || pos == clip.size() - 1 )
break;
char *end = 0;
values[comp] = strtod(&clip[pos], &end);
if( end == &clip[pos] || !end )
break;
pos = end - &clip[0];
}
if( comp < 4 )
@@ -355,51 +563,58 @@ namespace canvas
return;
}
float scale_x = 1,
scale_y = 1;
float width = values[1] - values[3],
height = values[2] - values[0];
CanvasPtr canvas = _canvas.lock();
if( canvas )
if( width < 0 || height < 0 )
{
// The scissor rectangle isn't affected by any transformation, so we need
// to convert to image/canvas coordinates on our selves.
scale_x = canvas->getSizeX()
/ static_cast<float>(canvas->getViewWidth());
scale_y = canvas->getSizeY()
/ static_cast<float>(canvas->getViewHeight());
SG_LOG(SG_GENERAL, SG_WARN, "Canvas: negative clip size: " << clip);
return;
}
osg::Scissor* scissor = new osg::Scissor();
if( !_scissor )
_scissor = new RelativeScissor(_transform.get());
// <top>, <right>, <bottom>, <left>
scissor->x() = scale_x * values[3];
scissor->y() = scale_y * values[0];
scissor->width() = scale_x * (values[1] - values[3]);
scissor->height() = scale_y * (values[2] - values[0]);
_scissor->x() = SGMiscf::roundToInt(values[3]);
_scissor->y() = SGMiscf::roundToInt(values[0]);
_scissor->width() = SGMiscf::roundToInt(width);
_scissor->height() = SGMiscf::roundToInt(height);
if( canvas )
// Canvas has y axis upside down
scissor->y() = canvas->getSizeY() - scissor->y() - scissor->height();
SGPropertyNode* clip_frame = _node->getChild("clip-frame", 0);
if( clip_frame )
valueChanged(clip_frame);
else
_scissor->_coord_reference = GLOBAL;
getOrCreateStateSet()->setAttributeAndModes(scissor);
ss->setAttributeAndModes(_scissor);
}
//----------------------------------------------------------------------------
void Element::setBoundingBox(const osg::BoundingBox& bb)
void Element::setClipFrame(ReferenceFrame rf)
{
if( _bounding_box.empty() )
{
SGPropertyNode* bb_node = _node->getChild("bounding-box", 0, true);
_bounding_box.resize(4);
_bounding_box[0] = bb_node->getChild("min-x", 0, true);
_bounding_box[1] = bb_node->getChild("min-y", 0, true);
_bounding_box[2] = bb_node->getChild("max-x", 0, true);
_bounding_box[3] = bb_node->getChild("max-y", 0, true);
}
if( _scissor )
_scissor->_coord_reference = rf;
}
_bounding_box[0]->setFloatValue(bb._min.x());
_bounding_box[1]->setFloatValue(bb._min.y());
_bounding_box[2]->setFloatValue(bb._max.x());
_bounding_box[3]->setFloatValue(bb._max.y());
//----------------------------------------------------------------------------
osg::BoundingBox Element::getBoundingBox() const
{
if( _drawable )
return _drawable->getBound();
osg::BoundingBox bb;
if( _transform.valid() )
bb.expandBy(_transform->getBound());
return bb;
}
//----------------------------------------------------------------------------
osg::BoundingBox Element::getTightBoundingBox() const
{
return getTransformedBounds(getMatrix());
}
//----------------------------------------------------------------------------
@@ -411,11 +626,83 @@ namespace canvas
osg::BoundingBox transformed;
const osg::BoundingBox& bb = _drawable->getBound();
for(int i = 0; i < 4; ++i)
transformed.expandBy( m * bb.corner(i) );
transformed.expandBy( bb.corner(i) * m );
return transformed;
}
//----------------------------------------------------------------------------
osg::Matrix Element::getMatrix() const
{
if( !_transform )
return osg::Matrix::identity();
if( !(_attributes_dirty & TRANSFORM) )
return _transform->getMatrix();
osg::Matrix m;
for( size_t i = 0; i < _transform_types.size(); ++i )
{
// Skip unused indizes...
if( _transform_types[i] == TT_NONE )
continue;
SGPropertyNode* tf_node = _node->getChild("tf", i, true);
// Build up the matrix representation of the current transform node
osg::Matrix tf;
switch( _transform_types[i] )
{
case TT_MATRIX:
tf = osg::Matrix( tf_node->getDoubleValue("m[0]", 1),
tf_node->getDoubleValue("m[1]", 0),
0,
tf_node->getDoubleValue("m[6]", 0),
tf_node->getDoubleValue("m[2]", 0),
tf_node->getDoubleValue("m[3]", 1),
0,
tf_node->getDoubleValue("m[7]", 0),
0,
0,
1,
0,
tf_node->getDoubleValue("m[4]", 0),
tf_node->getDoubleValue("m[5]", 0),
0,
tf_node->getDoubleValue("m[8]", 1) );
break;
case TT_TRANSLATE:
tf.makeTranslate( osg::Vec3f( tf_node->getDoubleValue("t[0]", 0),
tf_node->getDoubleValue("t[1]", 0),
0 ) );
break;
case TT_ROTATE:
tf.makeRotate( tf_node->getDoubleValue("rot", 0), 0, 0, 1 );
break;
case TT_SCALE:
{
float sx = tf_node->getDoubleValue("s[0]", 1);
// sy defaults to sx...
tf.makeScale( sx, tf_node->getDoubleValue("s[1]", sx), 1 );
break;
}
default:
break;
}
m.postMult( tf );
}
_transform->setMatrix(m);
_attributes_dirty &= ~TRANSFORM;
return m;
}
//----------------------------------------------------------------------------
Element::StyleSetters Element::_style_setters;
//----------------------------------------------------------------------------
Element::Element( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -424,11 +711,14 @@ namespace canvas
PropertyBasedElement(node),
_canvas( canvas ),
_parent( parent ),
_transform_dirty( false ),
_attributes_dirty( 0 ),
_transform( new osg::MatrixTransform ),
_style( parent_style ),
_scissor( 0 ),
_drawable( 0 )
{
staticInit();
SG_LOG
(
SG_GL,
@@ -436,7 +726,96 @@ namespace canvas
"New canvas element " << node->getPath()
);
addStyle("clip", &Element::setClip, this);
// Ensure elements are drawn in order they appear in the element tree
_transform->getOrCreateStateSet()
->setRenderBinDetails
(
0,
"PreOrderBin",
osg::StateSet::OVERRIDE_RENDERBIN_DETAILS
);
_transform->setUserData( new OSGUserData(this) );
}
//----------------------------------------------------------------------------
void Element::staticInit()
{
if( isInit<Element>() )
return;
addStyle("clip", "", &Element::setClip, false);
addStyle("clip-frame", "", &Element::setClipFrame, false);
addStyle("visible", "", &Element::setVisible, false);
}
//----------------------------------------------------------------------------
bool Element::isStyleEmpty(const SGPropertyNode* child) const
{
return !child
|| simgear::strutils::strip(child->getStringValue()).empty();
}
//----------------------------------------------------------------------------
bool Element::canApplyStyle(const SGPropertyNode* child) const
{
if( _node == child->getParent() )
return true;
// Parent values do not override if element has own value
return isStyleEmpty( _node->getChild(child->getName()) );
}
//----------------------------------------------------------------------------
bool Element::setStyleImpl( const SGPropertyNode* child,
const StyleInfo* style_info )
{
const StyleSetter* style_setter = style_info
? &style_info->setter
: getStyleSetter(child->getNameString());
while( style_setter )
{
if( style_setter->func(*this, child) )
return true;
style_setter = style_setter->next;
}
return false;
}
//----------------------------------------------------------------------------
const Element::StyleInfo*
Element::getStyleInfo(const std::string& name) const
{
StyleSetters::const_iterator setter = _style_setters.find(name);
if( setter == _style_setters.end() )
return 0;
return &setter->second;
}
//----------------------------------------------------------------------------
const Element::StyleSetter*
Element::getStyleSetter(const std::string& name) const
{
const StyleInfo* info = getStyleInfo(name);
return info ? &info->setter : 0;
}
//----------------------------------------------------------------------------
const SGPropertyNode*
Element::getParentStyle(const SGPropertyNode* child) const
{
// Try to get value from parent...
if( _parent )
{
Style::const_iterator style =
_parent->_style.find(child->getNameString());
if( style != _parent->_style.end() )
return style->second;
}
// ...or reset to default if none is available
return child; // TODO somehow get default value for each style?
}
//----------------------------------------------------------------------------
@@ -453,8 +832,12 @@ namespace canvas
//----------------------------------------------------------------------------
osg::StateSet* Element::getOrCreateStateSet()
{
return _drawable ? _drawable->getOrCreateStateSet()
: _transform->getOrCreateStateSet();
if( _drawable.valid() )
return _drawable->getOrCreateStateSet();
if( _transform.valid() )
return _transform->getOrCreateStateSet();
return 0;
}
//----------------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
// Interface for 2D Canvas elements
///@file Interface for 2D Canvas elements
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
@@ -23,13 +23,13 @@
#include <simgear/canvas/CanvasEvent.hxx>
#include <simgear/props/PropertyBasedElement.hxx>
#include <simgear/misc/stdint.hxx> // for uint32_t
#include <simgear/nasal/cppbind/Ghost.hxx>
#include <osg/BoundingBox>
#include <osg/MatrixTransform>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/type_traits/is_base_of.hpp>
namespace osg
{
@@ -41,28 +41,62 @@ namespace simgear
namespace canvas
{
/**
* Baseclass for Elements displayed inside a Canvas.
*/
class Element:
public PropertyBasedElement
{
public:
typedef boost::function<void(const SGPropertyNode*)> StyleSetter;
typedef std::map<std::string, StyleSetter> StyleSetters;
/**
* Remove the property listener of the element.
*
* You will need to call the appropriate methods (#childAdded,
* #childRemoved, #valueChanged) yourself to ensure the element still
* receives the needed events.
* Store pointer to window as user data
*/
void removeListener();
class OSGUserData:
public osg::Referenced
{
public:
ElementPtr element;
OSGUserData(ElementPtr element);
};
typedef boost::function<bool(Element&, const SGPropertyNode*)>
StyleSetterFunc;
typedef boost::function<void(Element&, const SGPropertyNode*)>
StyleSetterFuncUnchecked;
struct StyleSetter:
public SGReferenced
{
StyleSetterFunc func;
SGSharedPtr<StyleSetter> next;
};
struct StyleInfo
{
StyleSetter setter; ///< Function(s) for setting this style
std::string type; ///< Interpolation type
bool inheritable; ///< Whether children can inherit this style from
/// their parents
};
/**
* Coordinate reference frame (eg. "clip" property)
*/
enum ReferenceFrame
{
GLOBAL, ///< Global coordinates
PARENT, ///< Coordinates relative to parent coordinate frame
LOCAL ///< Coordinates relative to local coordinates (parent
/// coordinates with local transformations applied)
};
/**
*
*/
virtual ~Element() = 0;
virtual void onDestroy();
ElementWeakPtr getWeakPtr() const;
ElementPtr getParent() const;
CanvasWeakPtr getCanvas() const;
/**
* Called every frame to update internal state
@@ -71,19 +105,43 @@ namespace canvas
*/
virtual void update(double dt);
naRef addEventListener(const nasal::CallContext& ctx);
bool addEventListener(const std::string& type, const EventListener& cb);
virtual void clearEventListener();
virtual bool accept(EventVisitor& visitor);
virtual bool ascend(EventVisitor& visitor);
virtual bool traverse(EventVisitor& visitor);
void callListeners(const canvas::EventPtr& event);
virtual bool handleEvent(const EventPtr& event);
bool dispatchEvent(const EventPtr& event);
virtual bool hitBound( const osg::Vec2f& pos,
/**
*
* @param global_pos Position in global (canvas) coordinate frame
* @param parent_pos Position in parent coordinate frame
* @param local_pos Position in local (element) coordinate frame
*/
virtual bool hitBound( const osg::Vec2f& global_pos,
const osg::Vec2f& parent_pos,
const osg::Vec2f& local_pos ) const;
/**
* Set visibility of the element.
*/
void setVisible(bool visible);
osg::ref_ptr<osg::MatrixTransform> getMatrixTransform();
/**
* Get whether the element is visible or hidden.
*/
bool isVisible() const;
osg::MatrixTransform* getMatrixTransform();
osg::MatrixTransform const* getMatrixTransform() const;
/**
* Transform position to local coordinages.
*/
osg::Vec2f posToLocal(const osg::Vec2f& pos) const;
virtual void childAdded( SGPropertyNode * parent,
SGPropertyNode * child );
@@ -91,7 +149,8 @@ namespace canvas
SGPropertyNode * child );
virtual void valueChanged(SGPropertyNode * child);
virtual bool setStyle(const SGPropertyNode* child);
virtual bool setStyle( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
/**
* Set clipping shape
@@ -102,20 +161,57 @@ namespace canvas
void setClip(const std::string& clip);
/**
* Write the given bounding box to the property tree
* Clipping coordinates reference frame
*/
void setBoundingBox(const osg::BoundingBox& bb);
void setClipFrame(ReferenceFrame rf);
/**
* Get bounding box (may not be as tight as bounding box returned by
* #getTightBoundingBox)
*/
osg::BoundingBox getBoundingBox() const;
/**
* Get tight bounding box (child points are transformed to elements
* coordinate space before calculating the bounding box).
*/
osg::BoundingBox getTightBoundingBox() const;
/**
* Get bounding box with children/drawables transformed by passed matrix
*/
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
/**
* Get the transformation matrix (product of all transforms)
*/
osg::Matrix getMatrix() const;
/**
* Create an canvas Element
*
* @tparam Derived Type of element (needs to derive from Element)
*/
template<typename Derived>
static
typename boost::enable_if<
boost::is_base_of<Element, Derived>,
ElementPtr
>::type create( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style = Style(),
Element* parent = NULL )
{
return ElementPtr( new Derived(canvas, node, style, parent) );
}
protected:
enum Attributes
{
LAST_ATTRIBUTE = 0x0001
TRANSFORM = 1,
BLEND_FUNC = TRANSFORM << 1,
LAST_ATTRIBUTE = BLEND_FUNC << 1
};
enum TransformType
@@ -127,64 +223,330 @@ namespace canvas
TT_SCALE
};
class RelativeScissor;
CanvasWeakPtr _canvas;
Element *_parent;
uint32_t _attributes_dirty;
mutable uint32_t _attributes_dirty;
bool _transform_dirty;
osg::ref_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
osg::observer_ptr<osg::MatrixTransform> _transform;
std::vector<TransformType> _transform_types;
Style _style;
StyleSetters _style_setters;
std::vector<SGPropertyNode_ptr> _bounding_box;
Style _style;
RelativeScissor *_scissor;
typedef std::vector<EventListenerPtr> Listener;
typedef std::map<Event::Type, Listener> ListenerMap;
typedef std::vector<EventListener> Listener;
typedef std::map<int, Listener> ListenerMap;
ListenerMap _listener;
typedef std::map<std::string, StyleInfo> StyleSetters;
static StyleSetters _style_setters;
static void staticInit();
Element( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
Element* parent );
template<typename T, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T), C2 instance)
/**
* Returns false on first call and true on any successive call. Use to
* perform initialization tasks which are only required once per element
* type.
*
* @tparam Derived (Derived) class type
*/
template<class Derived>
static bool isInit()
{
return _style_setters[ name ] =
bindStyleSetter<T>(name, setter, instance);
static bool is_init = false;
if( is_init )
return true;
is_init = true;
return false;
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
addStyle(const std::string& name, void (C1::*setter)(T2), C2 instance)
{
return _style_setters[ name ] =
bindStyleSetter<T1>(name, setter, instance);
}
template<class C1, class C2>
Element::StyleSetter
/**
* Register a function for setting a style specified by the given property
*
* @param name Property name
* @param type Interpolation type
* @param setter Setter function
*
* @tparam T1 Type of value used to retrieve value from property
* node
* @tparam T2 Type of value the setter function expects
* @tparam Derived Type of class the setter can be applied to
*
* @note T1 needs to be convertible to T2
*/
template<
typename T1,
typename T2,
class Derived
>
static
StyleSetter
addStyle( const std::string& name,
void (C1::*setter)(const std::string&),
C2 instance )
const std::string& type,
const boost::function<void (Derived&, T2)>& setter,
bool inheritable = true )
{
return _style_setters[ name ] =
bindStyleSetter<const char*>(name, setter, instance);
StyleInfo& style_info = _style_setters[ name ];
if( !type.empty() )
{
if( !style_info.type.empty() && type != style_info.type )
SG_LOG
(
SG_GENERAL,
SG_WARN,
"changing animation type for '" << name << "': "
<< style_info.type << " -> " << type
);
style_info.type = type;
}
// TODO check if changed?
style_info.inheritable = inheritable;
StyleSetter* style = &style_info.setter;
while( style->next )
style = style->next;
if( style->func )
style = style->next = new StyleSetter;
style->func = boost::bind
(
&type_match<Derived>::call,
_1,
_2,
bindStyleSetter<T1>(name, setter)
);
return *style;
}
template<typename T1, typename T2, class C1, class C2>
Element::StyleSetter
bindStyleSetter( const std::string& name,
void (C1::*setter)(T2),
C2 instance )
template<
typename T,
class Derived
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
const boost::function<void (Derived&, T)>& setter,
bool inheritable = true )
{
return boost::bind(setter, instance, boost::bind(&getValue<T1>, _1));
return addStyle<T, T>(name, type, setter, inheritable);
}
template<
typename T,
class Derived
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(T),
bool inheritable = true )
{
return addStyle<T, T>
(
name,
type,
boost::function<void (Derived&, T)>(setter),
inheritable
);
}
template<
typename T1,
typename T2,
class Derived
>
static
StyleSetterFunc
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(T2),
bool inheritable = true )
{
return addStyle<T1>
(
name,
type,
boost::function<void (Derived&, T2)>(setter),
inheritable
);
}
template<
class Derived
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Derived::*setter)(const std::string&),
bool inheritable = true )
{
return addStyle<const char*, const std::string&>
(
name,
type,
boost::function<void (Derived&, const std::string&)>(setter),
inheritable
);
}
template<
typename T,
class Derived,
class Other,
class OtherRef
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(T),
OtherRef Derived::*instance_ref,
bool inheritable = true )
{
return addStyle<T, T>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
}
template<
typename T1,
typename T2,
class Derived,
class Other,
class OtherRef
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(T2),
OtherRef Derived::*instance_ref,
bool inheritable = true )
{
return addStyle<T1>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
}
template<
typename T1,
typename T2,
class Derived,
class Other,
class OtherRef
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
const boost::function<void (Other&, T2)>& setter,
OtherRef Derived::*instance_ref,
bool inheritable = true )
{
return addStyle<T1>
(
name,
type,
bindOther(setter, instance_ref),
inheritable
);
}
template<
class Derived,
class Other,
class OtherRef
>
static
StyleSetter
addStyle( const std::string& name,
const std::string& type,
void (Other::*setter)(const std::string&),
OtherRef Derived::*instance_ref,
bool inheritable = true )
{
return addStyle<const char*, const std::string&>
(
name,
type,
boost::function<void (Other&, const std::string&)>(setter),
instance_ref,
inheritable
);
}
template<typename T, class Derived, class Other, class OtherRef>
static
boost::function<void (Derived&, T)>
bindOther( void (Other::*setter)(T), OtherRef Derived::*instance_ref )
{
return boost::bind(setter, boost::bind(instance_ref, _1), _2);
}
template<typename T, class Derived, class Other, class OtherRef>
static
boost::function<void (Derived&, T)>
bindOther( const boost::function<void (Other&, T)>& setter,
OtherRef Derived::*instance_ref )
{
return boost::bind
(
setter,
boost::bind
(
&reference_from_pointer<Other, OtherRef>,
boost::bind(instance_ref, _1)
),
_2
);
}
template<typename T1, typename T2, class Derived>
static
StyleSetterFuncUnchecked
bindStyleSetter( const std::string& name,
const boost::function<void (Derived&, T2)>& setter )
{
return boost::bind
(
setter,
// We will only call setters with Derived instances, so we can safely
// cast here.
boost::bind(&derived_cast<Derived>, _1),
boost::bind(&getValue<T1>, _2)
);
}
bool isStyleEmpty(const SGPropertyNode* child) const;
bool canApplyStyle(const SGPropertyNode* child) const;
bool setStyleImpl( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
const StyleInfo* getStyleInfo(const std::string& name) const;
const StyleSetter* getStyleSetter(const std::string& name) const;
const SGPropertyNode* getParentStyle(const SGPropertyNode* child) const;
virtual void childAdded(SGPropertyNode * child) {}
virtual void childRemoved(SGPropertyNode * child){}
virtual void childChanged(SGPropertyNode * child){}
@@ -203,9 +565,63 @@ namespace canvas
osg::ref_ptr<osg::Drawable> _drawable;
Element(const Element&);// = delete
template<class Derived>
static Derived& derived_cast(Element& el)
{
return static_cast<Derived&>(el);
}
template<class T, class SharedPtr>
static T& reference_from_pointer(const SharedPtr& p)
{
return *get_pointer(p);
}
/**
* Helper to call a function only of the element type can be converted to
* the required type.
*
* @return Whether the function has been called
*/
template<class Derived>
struct type_match
{
static bool call( Element& el,
const SGPropertyNode* prop,
const StyleSetterFuncUnchecked& func )
{
Derived* d = dynamic_cast<Derived*>(&el);
if( !d )
return false;
func(*d, prop);
return true;
}
};
};
} // namespace canvas
template<>
struct enum_traits<canvas::Element::ReferenceFrame>
{
static const char* name()
{
return "canvas::Element::ReferenceFrame";
}
static canvas::Element::ReferenceFrame defVal()
{
return canvas::Element::GLOBAL;
}
static bool validate(int frame)
{
return frame >= canvas::Element::GLOBAL
&& frame <= canvas::Element::LOCAL;
}
};
} // namespace simgear
#endif /* CANVAS_ELEMENT_HXX_ */

View File

@@ -22,7 +22,7 @@
#include "CanvasPath.hxx"
#include "CanvasText.hxx"
#include <simgear/canvas/CanvasEventVisitor.hxx>
#include <simgear/canvas/MouseEvent.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
@@ -33,17 +33,37 @@ namespace simgear
namespace canvas
{
/**
* Create an canvas Element of type T
* Add canvas Element type to factory map
*/
template<typename T>
ElementPtr createElement( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& style,
Element* parent )
template<typename ElementType>
void add(ElementFactories& factories)
{
ElementPtr el( new T(canvas, node, style, parent) );
el->setSelf(el);
return el;
ElementType::staticInit();
factories[ElementType::TYPE_NAME] = &Element::create<ElementType>;
}
//----------------------------------------------------------------------------
ElementFactories Group::_child_factories;
const std::string Group::TYPE_NAME = "group";
void warnTransformExpired(const char* member_name)
{
SG_LOG( SG_GENERAL,
SG_WARN,
"canvas::Group::" << member_name << ": Group has expired." );
}
//----------------------------------------------------------------------------
void Group::staticInit()
{
if( isInit<Group>() )
return;
add<Group>(_child_factories);
add<Image>(_child_factories);
add<Map >(_child_factories);
add<Path >(_child_factories);
add<Text >(_child_factories);
}
//----------------------------------------------------------------------------
@@ -53,11 +73,7 @@ namespace canvas
Element* parent ):
Element(canvas, node, parent_style, parent)
{
_child_factories["group"] = &createElement<Group>;
_child_factories["image"] = &createElement<Image>;
_child_factories["map" ] = &createElement<Map >;
_child_factories["path" ] = &createElement<Path >;
_child_factories["text" ] = &createElement<Text >;
staticInit();
}
//----------------------------------------------------------------------------
@@ -80,25 +96,57 @@ namespace canvas
//----------------------------------------------------------------------------
ElementPtr Group::getChild(const SGPropertyNode* node)
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
return ElementPtr();
return findChild(node, "");
}
return child->second;
//----------------------------------------------------------------------------
ElementPtr Group::getChild(const std::string& id)
{
return findChild(0, id);
}
//----------------------------------------------------------------------------
ElementPtr Group::getOrCreateChild( const std::string& type,
const std::string& id )
{
ElementPtr child = getChild(id);
if( child )
{
if( child->getProps()->getNameString() == type )
return child;
SG_LOG
(
SG_GENERAL,
SG_WARN,
"Group::getOrCreateChild: type missmatch! "
"('" << type << "' != '" << child->getProps()->getName() << "', "
"id = '" << id << "')"
);
return ElementPtr();
}
return createChild(type, id);
}
//----------------------------------------------------------------------------
ElementPtr Group::getElementById(const std::string& id)
{
std::vector<GroupPtr> groups;
BOOST_FOREACH( ChildList::value_type child, _children )
if( !_transform.valid() )
{
const ElementPtr& el = child.second;
if( el->getProps()->getStringValue("id") == id )
warnTransformExpired("getElementById");
return ElementPtr();
}
std::vector<GroupPtr> groups;
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
const ElementPtr& el = getChildByIndex(i);
if( el->get<std::string>("id") == id )
return el;
GroupPtr group = boost::dynamic_pointer_cast<Group>(el);
Group* group = dynamic_cast<Group*>(el.get());
if( group )
groups.push_back(group);
}
@@ -113,11 +161,23 @@ namespace canvas
return ElementPtr();
}
//----------------------------------------------------------------------------
void Group::clearEventListener()
{
if( !_transform.valid() )
return warnTransformExpired("clearEventListener");
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->clearEventListener();
Element::clearEventListener();
}
//----------------------------------------------------------------------------
void Group::update(double dt)
{
BOOST_FOREACH( ChildList::value_type child, _children )
child.second->update(dt);
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
getChildByIndex(i)->update(dt);
Element::update(dt);
}
@@ -126,30 +186,32 @@ namespace canvas
bool Group::traverse(EventVisitor& visitor)
{
// Iterate in reverse order as last child is displayed on top
BOOST_REVERSE_FOREACH( ChildList::value_type child, _children )
for(size_t i = _transform->getNumChildren(); i --> 0;)
{
if( child.second->accept(visitor) )
if( getChildByIndex(i)->accept(visitor) )
return true;
}
return false;
}
//----------------------------------------------------------------------------
bool Group::setStyle(const SGPropertyNode* style)
bool Group::setStyle( const SGPropertyNode* style,
const StyleInfo* style_info )
{
// Don't propagate styles directly applicable to this group
if( Element::setStyle(style) )
return true;
if( style->getParent() != _node
&& _style.find(style->getNameString()) != _style.end() )
if( !canApplyStyle(style) )
return false;
bool handled = false;
BOOST_FOREACH( ChildList::value_type child, _children )
bool handled = setStyleImpl(style, style_info);
if( style_info->inheritable )
{
if( child.second->setStyle(style) )
handled = true;
if( !_transform.valid() )
{
warnTransformExpired("setStyle");
return false;
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
handled |= getChildByIndex(i)->setStyle(style, style_info);
}
return handled;
@@ -159,17 +221,23 @@ namespace canvas
osg::BoundingBox Group::getTransformedBounds(const osg::Matrix& m) const
{
osg::BoundingBox bb;
BOOST_FOREACH( ChildList::value_type child, _children )
if( !_transform.valid() )
{
if( !child.second->getMatrixTransform()->getNodeMask() )
warnTransformExpired("getTransformedBounds");
return bb;
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
const ElementPtr& child = getChildByIndex(i);
if( !child->getMatrixTransform()->getNodeMask() )
continue;
bb.expandBy
(
child.second->getTransformedBounds
child->getTransformedBounds
(
child.second->getMatrixTransform()->getMatrix() * m
child->getMatrixTransform()->getMatrix() * m
)
);
}
@@ -177,31 +245,42 @@ namespace canvas
return bb;
}
//----------------------------------------------------------------------------
ElementFactory Group::getChildFactory(const std::string& type) const
{
ElementFactories::iterator child_factory = _child_factories.find(type);
if( child_factory != _child_factories.end() )
return child_factory->second;
return ElementFactory();
}
//----------------------------------------------------------------------------
void Group::childAdded(SGPropertyNode* child)
{
if( child->getParent() != _node )
return;
ChildFactories::iterator child_factory =
_child_factories.find( child->getNameString() );
if( child_factory != _child_factories.end() )
ElementFactory child_factory = getChildFactory( child->getNameString() );
if( child_factory )
{
ElementPtr element = child_factory->second(_canvas, child, _style, this);
if( !_transform.valid() )
return warnTransformExpired("childAdded");
ElementPtr element = child_factory(_canvas, child, _style, this);
// Add to osg scene graph...
_transform->addChild( element->getMatrixTransform() );
_children.push_back( ChildList::value_type(child, element) );
// ...and ensure correct ordering
handleZIndexChanged(element);
return;
}
if( !Element::setStyle(child) )
{
// Only add style if not applicable to group itself
StyleInfo const* style = getStyleInfo(child->getNameString());
if( style && style->inheritable )
_style[ child->getNameString() ] = child;
setStyle(child);
}
}
//----------------------------------------------------------------------------
@@ -210,10 +289,15 @@ namespace canvas
if( node->getParent() != _node )
return;
if( _child_factories.find(node->getNameString()) != _child_factories.end() )
if( getChildFactory(node->getNameString()) )
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
if( !_transform.valid() )
// If transform is destroyed also all children are destroyed, so we can
// not do anything here.
return;
ElementPtr child = getChild(node);
if( !child )
SG_LOG
(
SG_GL,
@@ -222,71 +306,70 @@ namespace canvas
);
else
{
_transform->removeChild( child->second->getMatrixTransform() );
_children.erase(child);
// Remove child from the scenegraph (this automatically invalidates the
// reference on the element hold by the MatrixTransform)
child->onDestroy();
}
}
else
{
Style::iterator style = _style.find(node->getNameString());
if( style != _style.end() )
_style.erase(style);
_style.erase( node->getNameString() );
}
}
//----------------------------------------------------------------------------
void Group::childChanged(SGPropertyNode* node)
{
if( node->getParent()->getParent() == _node
SGPropertyNode* parent = node->getParent();
SGPropertyNode* grand_parent = parent ? parent->getParent() : NULL;
if( grand_parent == _node
&& node->getNameString() == "z-index" )
return handleZIndexChanged(node->getParent(), node->getIntValue());
return handleZIndexChanged(getChild(parent), node->getIntValue());
}
//----------------------------------------------------------------------------
void Group::handleZIndexChanged(SGPropertyNode* node, int z_index)
void Group::handleZIndexChanged(ElementPtr child, int z_index)
{
ChildList::iterator child = findChild(node);
if( child == _children.end() )
if( !child || !_transform.valid() )
return;
osg::Node* tf = child->second->getMatrixTransform();
int index = _transform->getChildIndex(tf),
index_new = index;
osg::ref_ptr<osg::MatrixTransform> tf = child->getMatrixTransform();
size_t index = _transform->getChildIndex(tf),
index_new = index;
ChildList::iterator next = child;
++next;
while( next != _children.end()
&& next->first->getIntValue("z-index", 0) <= z_index )
for(;; ++index_new)
{
++index_new;
++next;
if( index_new + 1 == _transform->getNumChildren() )
break;
// Move to end of block with same index (= move upwards until the next
// element has a higher index)
if( getChildByIndex(index_new + 1)->get<int>("z-index", 0) > z_index )
break;
}
if( index_new != index )
if( index_new == index )
{
_children.insert(next, *child);
}
else
{
ChildList::iterator prev = child;
while( prev != _children.begin()
&& (--prev)->first->getIntValue("z-index", 0) > z_index)
// We were not able to move upwards so now try downwards
for(;; --index_new)
{
--index_new;
if( index_new == 0 )
break;
// Move to end of block with same index (= move downwards until the
// previous element has the same or a lower index)
if( getChildByIndex(index_new - 1)->get<int>("z-index", 0) <= z_index )
break;
}
if( index == index_new )
return;
_children.insert(prev, *child);
}
_transform->removeChild(index);
_transform->insertChild(index_new, tf);
_children.erase(child);
SG_LOG
(
SG_GENERAL,
@@ -296,14 +379,42 @@ namespace canvas
}
//----------------------------------------------------------------------------
Group::ChildList::iterator Group::findChild(const SGPropertyNode* node)
ElementPtr Group::getChildByIndex(size_t index) const
{
return std::find_if
(
_children.begin(),
_children.end(),
boost::bind(&ChildList::value_type::first, _1) == node
);
assert(_transform.valid());
OSGUserData* ud =
static_cast<OSGUserData*>(_transform->getChild(index)->getUserData());
assert(ud);
return ud->element;
}
//----------------------------------------------------------------------------
ElementPtr Group::findChild( const SGPropertyNode* node,
const std::string& id ) const
{
if( !_transform.valid() )
{
warnTransformExpired("findChild");
return ElementPtr();
}
for(size_t i = 0; i < _transform->getNumChildren(); ++i)
{
ElementPtr el = getChildByIndex(i);
if( node )
{
if( el->getProps() == node )
return el;
}
else
{
if( el->get<std::string>("id") == id )
return el;
}
}
return ElementPtr();
}
} // namespace canvas

View File

@@ -28,10 +28,15 @@ namespace simgear
namespace canvas
{
typedef std::map<std::string, ElementFactory> ElementFactories;
class Group:
public Element
{
public:
static const std::string TYPE_NAME;
static void staticInit();
typedef std::list< std::pair< const SGPropertyNode*,
ElementPtr
>
@@ -46,6 +51,33 @@ namespace canvas
ElementPtr createChild( const std::string& type,
const std::string& id = "" );
ElementPtr getChild(const SGPropertyNode* node);
ElementPtr getChild(const std::string& id);
ElementPtr getOrCreateChild( const std::string& type,
const std::string& id );
template<class T>
SGSharedPtr<T> createChild(const std::string& id = "")
{
return dynamic_cast<T*>( createChild(T::TYPE_NAME, id).get() );
}
template<class T>
SGSharedPtr<T> getChild(const SGPropertyNode* node)
{
return dynamic_cast<T*>( getChild(node).get() );
}
template<class T>
SGSharedPtr<T> getChild(const std::string& id)
{
return dynamic_cast<T*>( getChild(id).get() );
}
template<class T>
SGSharedPtr<T> getOrCreateChild(const std::string& id)
{
return dynamic_cast<T*>( getOrCreateChild(T::TYPE_NAME, id).get() );
}
/**
* Get first child with given id (breadth-first search)
@@ -54,28 +86,36 @@ namespace canvas
*/
ElementPtr getElementById(const std::string& id);
virtual void clearEventListener();
virtual void update(double dt);
virtual bool traverse(EventVisitor& visitor);
virtual bool setStyle(const SGPropertyNode* child);
virtual bool setStyle( const SGPropertyNode* child,
const StyleInfo* style_info = 0 );
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
protected:
typedef std::map<std::string, ElementFactory> ChildFactories;
static ElementFactories _child_factories;
ChildFactories _child_factories;
ChildList _children;
/**
* Overload in derived classes to allow for more/other types of elements
* to be managed.
*/
virtual ElementFactory getChildFactory(const std::string& type) const;
virtual void childAdded(SGPropertyNode * child);
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);
void handleZIndexChanged(SGPropertyNode* node, int z_index);
void handleZIndexChanged(ElementPtr child, int z_index = 0);
ChildList::iterator findChild(const SGPropertyNode* node);
ElementPtr getChildByIndex(size_t index) const;
ElementPtr findChild( const SGPropertyNode* node,
const std::string& id ) const;
};
} // namespace canvas

View File

@@ -21,14 +21,15 @@
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasMgr.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx>
#include <simgear/canvas/events/MouseEvent.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <simgear/misc/sg_path.hxx>
#include <osg/Array>
#include <osg/Geometry>
#include <osg/PrimitiveSet>
#include <boost/algorithm/string/predicate.hpp>
#include <osgDB/Registry>
namespace simgear
{
@@ -43,9 +44,11 @@ namespace canvas
{
public:
CullCallback(const CanvasWeakPtr& canvas);
void cullNextFrame();
private:
CanvasWeakPtr _canvas;
mutable bool _cull_next_frame;
virtual bool cull( osg::NodeVisitor* nv,
osg::Drawable* drawable,
@@ -54,21 +57,49 @@ namespace canvas
//----------------------------------------------------------------------------
CullCallback::CullCallback(const CanvasWeakPtr& canvas):
_canvas( canvas )
_canvas( canvas ),
_cull_next_frame( false )
{
}
//----------------------------------------------------------------------------
void CullCallback::cullNextFrame()
{
_cull_next_frame = true;
}
//----------------------------------------------------------------------------
bool CullCallback::cull( osg::NodeVisitor* nv,
osg::Drawable* drawable,
osg::RenderInfo* renderInfo ) const
{
if( !_canvas.expired() )
_canvas.lock()->enableRendering();
CanvasPtr canvas = _canvas.lock();
if( canvas )
canvas->enableRendering();
// TODO check if window/image should be culled
return false;
if( !_cull_next_frame )
// TODO check if window/image should be culled
return false;
_cull_next_frame = false;
return true;
}
//----------------------------------------------------------------------------
const std::string Image::TYPE_NAME = "image";
//----------------------------------------------------------------------------
void Image::staticInit()
{
if( isInit<Image>() )
return;
addStyle("fill", "color", &Image::setFill);
addStyle("outset", "", &Image::setOutset);
addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
addStyle("slice", "", &Image::setSlice);
addStyle("slice-width", "", &Image::setSliceWidth);
}
//----------------------------------------------------------------------------
@@ -82,6 +113,8 @@ namespace canvas
_src_rect(0,0),
_region(0,0)
{
staticInit();
_geom = new osg::Geometry;
_geom->setUseDisplayList(false);
@@ -91,35 +124,33 @@ namespace canvas
// allocate arrays for the image
_vertices = new osg::Vec3Array(4);
_vertices->setDataVariance(osg::Object::STATIC);
_vertices->setDataVariance(osg::Object::DYNAMIC);
_geom->setVertexArray(_vertices);
_texCoords = new osg::Vec2Array(4);
_texCoords->setDataVariance(osg::Object::STATIC);
_geom->setTexCoordArray(0, _texCoords);
_texCoords->setDataVariance(osg::Object::DYNAMIC);
_geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
_colors = new osg::Vec4Array(4);
_colors->setDataVariance(osg::Object::STATIC);
_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
_geom->setColorArray(_colors);
_colors = new osg::Vec4Array(1);
_colors->setDataVariance(osg::Object::DYNAMIC);
_geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
osg::DrawArrays* prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
prim->set(osg::PrimitiveSet::QUADS, 0, 4);
prim->setDataVariance(osg::Object::STATIC);
_geom->addPrimitiveSet(prim);
_prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
_prim->set(osg::PrimitiveSet::QUADS, 0, 4);
_prim->setDataVariance(osg::Object::DYNAMIC);
_geom->addPrimitiveSet(_prim);
setDrawable(_geom);
addStyle("fill", &Image::setFill, this);
setFill("#ffffff"); // TODO how should we handle default values?
setupStyle();
}
//----------------------------------------------------------------------------
Image::~Image()
{
if( _http_request )
_http_request->abort("image destroyed");
}
//----------------------------------------------------------------------------
@@ -127,61 +158,288 @@ namespace canvas
{
Element::update(dt);
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
(
_geom->getOrCreateStateSet()
->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
);
simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
if( (_attributes_dirty & SRC_CANVAS)
// check if texture has changed (eg. due to resizing)
|| (canvas && texture != canvas->getTexture()) )
{
_geom->getOrCreateStateSet()
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
if( !canvas || canvas->isInit() )
_attributes_dirty &= ~SRC_CANVAS;
}
if( !_attributes_dirty )
return;
const SGRect<int>& tex_dim = getTextureDimensions();
// http://www.w3.org/TR/css3-background/#border-image-slice
// The fill keyword, if present, causes the middle part of the image to be
// preserved. (By default it is discarded, i.e., treated as empty.)
bool fill = (_slice.getKeyword() == "fill");
if( _attributes_dirty & DEST_SIZE )
{
(*_vertices)[0].set(_region.l(), _region.t(), 0);
(*_vertices)[1].set(_region.r(), _region.t(), 0);
(*_vertices)[2].set(_region.r(), _region.b(), 0);
(*_vertices)[3].set(_region.l(), _region.b(), 0);
_vertices->dirty();
size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
if( num_vertices != _prim->getNumPrimitives() )
{
_vertices->resize(num_vertices);
_texCoords->resize(num_vertices);
_prim->setCount(num_vertices);
_prim->dirty();
_attributes_dirty |= SRC_RECT;
}
// http://www.w3.org/TR/css3-background/#border-image-outset
SGRect<float> region = _region;
if( _outset.isValid() )
{
const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
region.t() -= outset.t;
region.r() += outset.r;
region.b() += outset.b;
region.l() -= outset.l;
}
if( !_slice.isValid() )
{
setQuad(0, region.getMin(), region.getMax());
if( !_preserve_aspect_ratio.scaleToFill() )
// We need to update texture coordinates to keep the aspect ratio
_attributes_dirty |= SRC_RECT;
}
else
{
/*
Image slice, 9-scale, whatever it is called. The four corner images
stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
fill the remaining space up to the specified size.
x[0] x[1] x[2] x[3]
| | | |
-------------------- - y[0]
| tl | top | tr |
-------------------- - y[1]
| | | |
| l | | r |
| e | center | i |
| f | | g |
| t | | h |
| | | t |
-------------------- - y[2]
| bl | bottom | br |
-------------------- - y[3]
*/
const CSSBorder::Offsets& slice =
(_slice_width.isValid() ? _slice_width : _slice)
.getAbsOffsets(tex_dim);
float x[4] = {
region.l(),
region.l() + slice.l,
region.r() - slice.r,
region.r()
};
float y[4] = {
region.t(),
region.t() + slice.t,
region.b() - slice.b,
region.b()
};
int i = 0;
for(int ix = 0; ix < 3; ++ix)
for(int iy = 0; iy < 3; ++iy)
{
if( ix == 1 && iy == 1 && !fill )
// The fill keyword, if present, causes the middle part of the
// image to be filled.
continue;
setQuad( i++,
SGVec2f(x[ix ], y[iy ]),
SGVec2f(x[ix + 1], y[iy + 1]) );
}
}
_vertices->dirty();
_attributes_dirty &= ~DEST_SIZE;
_geom->dirtyBound();
setBoundingBox(_geom->getBound());
}
if( _attributes_dirty & SRC_RECT )
{
double u0 = _src_rect.l(),
u1 = _src_rect.r(),
v0 = _src_rect.b(),
v1 = _src_rect.t();
SGRect<float> src_rect = _src_rect;
if( !_node_src_rect->getBoolValue("normalized", true) )
{
const SGRect<int>& tex_dim = getTextureDimensions();
u0 /= tex_dim.width();
u1 /= tex_dim.width();
v0 /= tex_dim.height();
v1 /= tex_dim.height();
src_rect.t() /= tex_dim.height();
src_rect.r() /= tex_dim.width();
src_rect.b() /= tex_dim.height();
src_rect.l() /= tex_dim.width();
}
(*_texCoords)[0].set(u0, v0);
(*_texCoords)[1].set(u1, v0);
(*_texCoords)[2].set(u1, v1);
(*_texCoords)[3].set(u0, v1);
_texCoords->dirty();
// Image coordinate systems y-axis is flipped
std::swap(src_rect.t(), src_rect.b());
if( !_slice.isValid() )
{
// Image scaling preserving aspect ratio. Change texture coordinates to
// scale image accordingly.
//
// TODO allow to specify what happens to not filled space (eg. color,
// or texture repeat/mirror)
//
// http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
if( !_preserve_aspect_ratio.scaleToFill() )
{
osg::BoundingBox const& bb = getBoundingBox();
float dst_width = bb._max.x() - bb._min.x(),
dst_height = bb._max.y() - bb._min.y();
float scale_x = dst_width / tex_dim.width(),
scale_y = dst_height / tex_dim.height();
float scale = _preserve_aspect_ratio.scaleToFit()
? std::min(scale_x, scale_y)
: std::max(scale_x, scale_y);
if( scale_x != scale )
{
float d = scale_x / scale - 1;
if( _preserve_aspect_ratio.alignX()
== SVGpreserveAspectRatio::ALIGN_MIN )
{
src_rect.r() += d;
}
else if( _preserve_aspect_ratio.alignX()
== SVGpreserveAspectRatio::ALIGN_MAX )
{
src_rect.l() -= d;
}
else
{
src_rect.l() -= d / 2;
src_rect.r() += d / 2;
}
}
if( scale_y != scale )
{
float d = scale_y / scale - 1;
if( _preserve_aspect_ratio.alignY()
== SVGpreserveAspectRatio::ALIGN_MIN )
{
src_rect.b() -= d;
}
else if( _preserve_aspect_ratio.alignY()
== SVGpreserveAspectRatio::ALIGN_MAX )
{
src_rect.t() += d;
}
else
{
src_rect.t() += d / 2;
src_rect.b() -= d / 2;
}
}
}
setQuadUV(0, src_rect.getMin(), src_rect.getMax());
}
else
{
const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
float x[4] = {
src_rect.l(),
src_rect.l() + slice.l,
src_rect.r() - slice.r,
src_rect.r()
};
float y[4] = {
src_rect.t(),
src_rect.t() - slice.t,
src_rect.b() + slice.b,
src_rect.b()
};
int i = 0;
for(int ix = 0; ix < 3; ++ix)
for(int iy = 0; iy < 3; ++iy)
{
if( ix == 1 && iy == 1 && !fill )
// The fill keyword, if present, causes the middle part of the
// image to be filled.
continue;
setQuadUV( i++,
SGVec2f(x[ix ], y[iy ]),
SGVec2f(x[ix + 1], y[iy + 1]) );
}
}
_texCoords->dirty();
_attributes_dirty &= ~SRC_RECT;
}
}
//----------------------------------------------------------------------------
void Image::valueChanged(SGPropertyNode* child)
{
// If the image is switched from invisible to visible, and it shows a
// canvas, we need to delay showing it by one frame to ensure the canvas is
// updated before the image is displayed.
//
// As canvas::Element handles and filters changes to the "visible" property
// we can not check this in Image::childChanged but instead have to override
// Element::valueChanged.
if( !isVisible()
&& child->getParent() == _node
&& child->getNameString() == "visible"
&& child->getBoolValue() )
{
CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
if( cb )
cb->cullNextFrame();
}
Element::valueChanged(child);
}
//----------------------------------------------------------------------------
void Image::setSrcCanvas(CanvasPtr canvas)
{
if( !_src_canvas.expired() )
_src_canvas.lock()->removeDependentCanvas(_canvas);
CanvasPtr src_canvas = _src_canvas.lock(),
self_canvas = _canvas.lock();
_src_canvas = canvas;
_geom->getOrCreateStateSet()
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
if( src_canvas )
src_canvas->removeParentCanvas(self_canvas);
if( self_canvas )
self_canvas->removeChildCanvas(src_canvas);
_src_canvas = src_canvas = canvas;
_attributes_dirty |= SRC_CANVAS;
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
if( !_src_canvas.expired() )
if( src_canvas )
{
setupDefaultDimensions();
_src_canvas.lock()->addDependentCanvas(_canvas);
if( self_canvas )
{
self_canvas->addChildCanvas(src_canvas);
src_canvas->addParentCanvas(self_canvas);
}
}
}
@@ -197,7 +455,10 @@ namespace canvas
// remove canvas...
setSrcCanvas( CanvasPtr() );
_texture->setResizeNonPowerOfTwoHint(false);
_texture->setImage(img);
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
_geom->getOrCreateStateSet()
->setTextureAttributeAndModes(0, _texture);
@@ -208,21 +469,88 @@ namespace canvas
//----------------------------------------------------------------------------
void Image::setFill(const std::string& fill)
{
osg::Vec4 color;
if( !parseColor(fill, color) )
osg::Vec4 color(1,1,1,1);
if( !fill.empty() // If no color is given default to white
&& !parseColor(fill, color) )
return;
for( int i = 0; i < 4; ++i )
(*_colors)[i] = color;
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = CSSBorder::parse(outset);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setPreserveAspectRatio(const std::string& scale)
{
_preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
_attributes_dirty |= SRC_RECT;
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{
_slice = CSSBorder::parse(slice);
_attributes_dirty |= SRC_RECT | DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setSliceWidth(const std::string& width)
{
_slice_width = CSSBorder::parse(width);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const
{
return _region;
}
//----------------------------------------------------------------------------
bool Image::handleEvent(const EventPtr& event)
{
bool handled = Element::handleEvent(event);
CanvasPtr src_canvas = _src_canvas.lock();
if( !src_canvas )
return handled;
MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
if( mouse_event )
{
mouse_event.reset( new MouseEvent(*mouse_event) );
mouse_event->client_pos = mouse_event->local_pos
- toOsg(_region.getMin());
osg::Vec2f size(_region.width(), _region.height());
if( _outset.isValid() )
{
CSSBorder::Offsets outset =
_outset.getAbsOffsets(getTextureDimensions());
mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
size.x() += outset.l + outset.r;
size.y() += outset.t + outset.b;
}
// Scale event pos according to canvas view size vs. displayed/screen size
mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
mouse_event->local_pos = mouse_event->client_pos;
handled |= src_canvas->handleMouseEvent(mouse_event);
}
return handled;
}
//----------------------------------------------------------------------------
void Image::childChanged(SGPropertyNode* child)
{
@@ -265,20 +593,41 @@ namespace canvas
_attributes_dirty |= DEST_SIZE;
}
else if( name == "file" )
else if( name == "src" || name == "file" )
{
static const std::string CANVAS_PROTOCOL = "canvas://";
const std::string& path = child->getStringValue();
if( name == "file" )
SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
CanvasPtr canvas = _canvas.lock();
if( !canvas )
// Abort pending request
if( _http_request )
{
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
return;
_http_request->abort("setting new image");
_http_request.reset();
}
if( boost::starts_with(path, CANVAS_PROTOCOL) )
static const std::string PROTOCOL_SEP = "://";
std::string url = child->getStringValue(),
protocol, path;
size_t sep_pos = url.find(PROTOCOL_SEP);
if( sep_pos != std::string::npos )
{
protocol = url.substr(0, sep_pos);
path = url.substr(sep_pos + PROTOCOL_SEP.length());
}
else
path = url;
if( protocol == "canvas" )
{
CanvasPtr canvas = _canvas.lock();
if( !canvas )
{
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
return;
}
CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
if( !canvas_mgr )
{
@@ -289,7 +638,7 @@ namespace canvas
const SGPropertyNode* canvas_node =
canvas_mgr->getPropertyRoot()
->getParent()
->getNode( path.substr(CANVAS_PROTOCOL.size()) );
->getNode( path );
if( !canvas_node )
{
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
@@ -307,9 +656,19 @@ namespace canvas
setSrcCanvas(src_canvas);
}
else if( protocol == "http" || protocol == "https" )
// TODO check https
{
_http_request =
Canvas::getSystemAdapter()
->getHTTPClient()
->load(url)
// TODO handle capture of 'this'
->done(this, &Image::handleImageLoadDone);
}
else
{
setImage( canvas->getSystemAdapter()->getImage(path) );
setImage( Canvas::getSystemAdapter()->getImage(path) );
}
}
}
@@ -319,36 +678,127 @@ namespace canvas
{
if( !_src_rect.width() || !_src_rect.height() )
{
const SGRect<int>& tex_dim = getTextureDimensions();
_node_src_rect->setBoolValue("normalized", false);
_node_src_rect->setFloatValue("right", tex_dim.width());
_node_src_rect->setFloatValue("bottom", tex_dim.height());
// Show whole image by default
_node_src_rect->setBoolValue("normalized", true);
_node_src_rect->setFloatValue("right", 1);
_node_src_rect->setFloatValue("bottom", 1);
}
if( !_region.width() || !_region.height() )
{
_node->setFloatValue("size[0]", _src_rect.width());
_node->setFloatValue("size[1]", _src_rect.height());
// Default to image size.
// TODO handle showing only part of image?
const SGRect<int>& dim = getTextureDimensions();
_node->setFloatValue("size[0]", dim.width());
_node->setFloatValue("size[1]", dim.height());
}
}
//----------------------------------------------------------------------------
SGRect<int> Image::getTextureDimensions() const
{
osg::Texture2D *texture = !_src_canvas.expired()
? _src_canvas.lock()->getTexture()
: _texture.get();
CanvasPtr canvas = _src_canvas.lock();
SGRect<int> dim(0,0);
if( !texture )
return SGRect<int>();
// Use canvas/image dimensions rather than texture dimensions, as they could
// be resized internally by OpenSceneGraph (eg. to nearest power of two).
if( canvas )
{
dim.setRight( canvas->getViewWidth() );
dim.setBottom( canvas->getViewHeight() );
}
else if( _texture )
{
osg::Image* img = _texture->getImage();
return SGRect<int>
(
0,0,
texture->getTextureWidth(),
texture->getTextureHeight()
);
if( img )
{
dim.setRight( img->s() );
dim.setBottom( img->t() );
}
}
return dim;
}
//----------------------------------------------------------------------------
void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
{
int i = index * 4;
(*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
(*_vertices)[i + 1].set(br.x(), tl.y(), 0);
(*_vertices)[i + 2].set(br.x(), br.y(), 0);
(*_vertices)[i + 3].set(tl.x(), br.y(), 0);
}
//----------------------------------------------------------------------------
void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
{
int i = index * 4;
(*_texCoords)[i + 0].set(tl.x(), tl.y());
(*_texCoords)[i + 1].set(br.x(), tl.y());
(*_texCoords)[i + 2].set(br.x(), br.y());
(*_texCoords)[i + 3].set(tl.x(), br.y());
}
//----------------------------------------------------------------------------
void Image::handleImageLoadDone(HTTP::Request* req)
{
// Ignore stale/expired requests
if( _http_request != req )
return;
_http_request.reset();
if( req->responseCode() != 200 )
{
SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
<< req->responseReason());
return;
}
const std::string ext = SGPath(req->path()).extension(),
mime = req->responseMime();
SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
" (ext=" << ext << ", MIME=" << mime << ")");
const std::string& img_data =
static_cast<HTTP::MemoryRequest*>(req)->responseBody();
osgDB::Registry* reg = osgDB::Registry::instance();
// First try to detect image type by extension
osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
if( rw && loadImage(*rw, img_data, *req, "extension") )
return;
// Now try with MIME type
rw = reg->getReaderWriterForMimeType(mime);
if( rw && loadImage(*rw, img_data, *req, "MIME type") )
return;
SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
}
//----------------------------------------------------------------------------
bool Image::loadImage( osgDB::ReaderWriter& reader,
const std::string& data,
HTTP::Request& request,
const std::string& type )
{
SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
std::istringstream data_strm(data);
osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
if( result.success() )
{
setImage( result.takeImage() );
return true;
}
SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
<< result.message());
return false;
}
} // namespace canvas

View File

@@ -22,11 +22,14 @@
#include "CanvasElement.hxx"
#include <simgear/canvas/canvas_fwd.hxx>
#include <simgear/math/SGRect.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/misc/CSSBorder.hxx>
#include <simgear/misc/SVGpreserveAspectRatio.hxx>
#include <osg/Texture2D>
namespace simgear
{
namespace HTTP { class Request; }
namespace canvas
{
@@ -34,6 +37,9 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
static void staticInit();
/**
* @param node Property node containing settings for this image:
* rect/[left/right/top/bottom] Dimensions of source
@@ -43,11 +49,12 @@ namespace canvas
*/
Image( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
const Style& parent_style = Style(),
Element* parent = 0 );
virtual ~Image();
virtual void update(double dt);
virtual void valueChanged(SGPropertyNode* child);
void setSrcCanvas(CanvasPtr canvas);
CanvasWeakPtr getSrcCanvas() const;
@@ -55,14 +62,46 @@ namespace canvas
void setImage(osg::Image *img);
void setFill(const std::string& fill);
/**
* @see http://www.w3.org/TR/css3-background/#border-image-outset
*/
void setOutset(const std::string& outset);
/**
* @see
* http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
*/
void setPreserveAspectRatio(const std::string& scale);
/**
* Set image slice (aka. 9-scale)
*
* @see http://www.w3.org/TR/css3-background/#border-image-slice
*/
void setSlice(const std::string& slice);
/**
* Set image slice width.
*
* By default the size of the 9-scale grid is the same as specified
* with setSlice/"slice". Using this method allows setting values
* different to the size in the source image.
*
* @see http://www.w3.org/TR/css3-background/#border-image-width
*/
void setSliceWidth(const std::string& width);
const SGRect<float>& getRegion() const;
bool handleEvent(const EventPtr& event);
protected:
enum ImageAttributes
{
SRC_RECT = LAST_ATTRIBUTE << 1, // Source image rectangle
DEST_SIZE = SRC_RECT << 1 // Element size
DEST_SIZE = SRC_RECT << 1, // Element size
SRC_CANVAS = DEST_SIZE << 1
};
virtual void childChanged(SGPropertyNode * child);
@@ -70,11 +109,22 @@ namespace canvas
void setupDefaultDimensions();
SGRect<int> getTextureDimensions() const;
void setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br);
void setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br);
void handleImageLoadDone(HTTP::Request*);
bool loadImage( osgDB::ReaderWriter& reader,
const std::string& data,
HTTP::Request& request,
const std::string& type );
osg::ref_ptr<osg::Texture2D> _texture;
// TODO optionally forward events to canvas
CanvasWeakPtr _src_canvas;
HTTP::Request_ptr _http_request;
osg::ref_ptr<osg::Geometry> _geom;
osg::ref_ptr<osg::DrawArrays>_prim;
osg::ref_ptr<osg::Vec3Array> _vertices;
osg::ref_ptr<osg::Vec2Array> _texCoords;
osg::ref_ptr<osg::Vec4Array> _colors;
@@ -82,6 +132,12 @@ namespace canvas
SGPropertyNode *_node_src_rect;
SGRect<float> _src_rect,
_region;
SVGpreserveAspectRatio _preserve_aspect_ratio;
CSSBorder _outset,
_slice,
_slice_width;
};
} // namespace canvas

View File

@@ -42,7 +42,20 @@ namespace simgear
namespace canvas
{
//----------------------------------------------------------------------------
const std::string GEO = "-geo";
const std::string Map::TYPE_NAME = "map";
//----------------------------------------------------------------------------
void Map::staticInit()
{
Group::staticInit();
if( isInit<Map>() )
return;
// Do some initialization if needed...
}
//----------------------------------------------------------------------------
Map::Map( const CanvasWeakPtr& canvas,
@@ -54,7 +67,7 @@ namespace canvas
_projection(new SansonFlamsteedProjection),
_projection_dirty(true)
{
staticInit();
}
//----------------------------------------------------------------------------
@@ -192,6 +205,8 @@ namespace canvas
_projection->setOrientation(child->getFloatValue());
else if( child->getNameString() == "range" )
_projection->setRange(child->getDoubleValue());
else if( child->getNameString() == "screen-range" )
_projection->setScreenRange(child->getDoubleValue());
else
return Group::childChanged(child);

View File

@@ -35,6 +35,9 @@ namespace canvas
public Group
{
public:
static const std::string TYPE_NAME;
static void staticInit();
Map( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,

View File

@@ -20,7 +20,6 @@
#include <simgear/scene/util/parse_color.hxx>
#include <osg/Drawable>
#include <osg/BlendFunc>
#include <vg/openvg.h>
#include <cassert>
@@ -111,7 +110,7 @@ namespace canvas
*/
void setFill(const std::string& fill)
{
if( fill == "none" )
if( fill.empty() || fill == "none" )
{
_mode &= ~VG_FILL_PATH;
}
@@ -151,7 +150,7 @@ namespace canvas
*/
void setStroke(const std::string& stroke)
{
if( stroke == "none" )
if( stroke.empty() || stroke == "none" )
{
_mode &= ~VG_STROKE_PATH;
}
@@ -218,9 +217,10 @@ namespace canvas
state->setClientActiveTextureUnit(0);
state->disableAllVertexArrays();
glPushAttrib(~0u); // Don't use GL_ALL_ATTRIB_BITS as on my machine it
// eg. doesn't include GL_MULTISAMPLE_BIT
glPushClientAttrib(~0u);
bool was_blend_enabled = state->getLastAppliedMode(GL_BLEND);
bool was_stencil_enabled = state->getLastAppliedMode(GL_STENCIL_TEST);
osg::StateAttribute const* blend_func =
state->getLastAppliedAttribute(osg::StateAttribute::BLENDFUNC);
// Initialize/Update the paint
if( _attributes_dirty & STROKE_COLOR )
@@ -270,15 +270,19 @@ namespace canvas
if( err != VG_NO_ERROR )
SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
glPopAttrib();
glPopClientAttrib();
// Restore OpenGL state (TODO check if more is needed or integrate
// better with OpenSceneGraph)
if( was_blend_enabled ) glEnable(GL_BLEND);
if( was_stencil_enabled ) glEnable(GL_STENCIL_TEST);
if( blend_func ) blend_func->apply(*state);
}
osg::BoundingBox getTransformedBounds(const osg::Matrix& mat) const
{
osg::BoundingBox bb;
osg::Vec2f cur; // VG "Current point" (in local coordinates)
osg::Vec2f cur(0, 0), // VG "Current point" (in local coordinates)
sub(0, 0); // beginning of current sub path
VGubyte cmd_index = 0;
for( size_t i = 0, ci = 0;
i < _cmds.size() && ci < _coords.size();
@@ -298,6 +302,7 @@ namespace canvas
switch( cmd )
{
case VG_CLOSE_PATH:
cur = sub;
break;
case VG_MOVE_TO:
case VG_LINE_TO:
@@ -353,7 +358,12 @@ namespace canvas
}
if( num_coords > 0 )
{
cur = points[ num_coords - 1 ];
if( cmd == VG_MOVE_TO )
sub = cur;
}
}
return bb;
@@ -375,14 +385,11 @@ namespace canvas
// vgPathBounds doesn't take stroke width into account
float ext = 0.5 * _stroke_width;
osg::BoundingBox bb
return osg::BoundingBox
(
min[0] - ext, min[1] - ext, -0.1,
min[0] + size[0] + ext, min[1] + size[1] + ext, 0.1
);
_path_element->setBoundingBox(bb);
return bb;
}
private:
@@ -429,6 +436,9 @@ namespace canvas
*/
void update()
{
if( !vgHasContextSH() )
return;
if( _attributes_dirty & PATH )
{
const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
@@ -454,7 +464,12 @@ namespace canvas
}
if( _attributes_dirty & BOUNDING_BOX )
{
dirtyBound();
// Recalculate bounding box now (prevent race condition)
getBound();
}
}
struct PathUpdateCallback:
@@ -467,6 +482,25 @@ namespace canvas
};
};
//----------------------------------------------------------------------------
const std::string Path::TYPE_NAME = "path";
//----------------------------------------------------------------------------
void Path::staticInit()
{
if( isInit<Path>() )
return;
PathDrawableRef Path::*path = &Path::_path;
addStyle("fill", "color", &PathDrawable::setFill, path);
addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
addStyle("stroke", "color", &PathDrawable::setStroke, path);
addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
}
//----------------------------------------------------------------------------
Path::Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -475,16 +509,9 @@ namespace canvas
Element(canvas, node, parent_style, parent),
_path( new PathDrawable(this) )
{
staticInit();
setDrawable(_path);
PathDrawable *path = _path.get();
addStyle("fill", &PathDrawable::setFill, path);
addStyle("fill-rule", &PathDrawable::setFillRule, path);
addStyle("stroke", &PathDrawable::setStroke, path);
addStyle("stroke-width", &PathDrawable::setStrokeWidth, path);
addStyle("stroke-dasharray", &PathDrawable::setStrokeDashArray, path);
addStyle("stroke-linecap", &PathDrawable::setStrokeLinecap, path);
setupStyle();
}
@@ -517,6 +544,60 @@ namespace canvas
return _path->getTransformedBounds(m);
}
//----------------------------------------------------------------------------
Path& Path::moveTo(float x_abs, float y_abs)
{
return addSegment(VG_MOVE_TO_ABS, x_abs, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::move(float x_rel, float y_rel)
{
return addSegment(VG_MOVE_TO_REL, x_rel, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::lineTo(float x_abs, float y_abs)
{
return addSegment(VG_LINE_TO_ABS, x_abs, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::line(float x_rel, float y_rel)
{
return addSegment(VG_LINE_TO_REL, x_rel, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::horizTo(float x_abs)
{
return addSegment(VG_HLINE_TO_ABS, x_abs);
}
//----------------------------------------------------------------------------
Path& Path::horiz(float x_rel)
{
return addSegment(VG_HLINE_TO_REL, x_rel);
}
//----------------------------------------------------------------------------
Path& Path::vertTo(float y_abs)
{
return addSegment(VG_VLINE_TO_ABS, y_abs);
}
//----------------------------------------------------------------------------
Path& Path::vert(float y_rel)
{
return addSegment(VG_VLINE_TO_REL, y_rel);
}
//----------------------------------------------------------------------------
Path& Path::close()
{
return addSegment(VG_CLOSE_PATH);
}
//----------------------------------------------------------------------------
void Path::childRemoved(SGPropertyNode* child)
{

View File

@@ -20,6 +20,7 @@
#define CANVAS_PATH_HXX_
#include "CanvasElement.hxx"
#include <boost/preprocessor/iteration/iterate.hpp>
namespace simgear
{
@@ -29,6 +30,9 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
static void staticInit();
Path( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
@@ -39,6 +43,30 @@ namespace canvas
virtual osg::BoundingBox getTransformedBounds(const osg::Matrix& m) const;
#define BOOST_PP_ITERATION_LIMITS (0, 6)
#define BOOST_PP_FILENAME_1 \
<simgear/canvas/elements/detail/add_segment_variadic.hxx>
#include BOOST_PP_ITERATE()
/** Move path cursor */
Path& moveTo(float x_abs, float y_abs);
Path& move(float x_rel, float y_rel);
/** Add a line */
Path& lineTo(float x_abs, float y_abs);
Path& line(float x_rel, float y_rel);
/** Add a horizontal line */
Path& horizTo(float x_abs);
Path& horiz(float x_rel);
/** Add a vertical line */
Path& vertTo(float y_abs);
Path& vert(float y_rel);
/** Close the path (implicit lineTo to first point of path) */
Path& close();
protected:
enum PathAttributes
@@ -48,7 +76,8 @@ namespace canvas
};
class PathDrawable;
osg::ref_ptr<PathDrawable> _path;
typedef osg::ref_ptr<PathDrawable> PathDrawableRef;
PathDrawableRef _path;
virtual void childRemoved(SGPropertyNode * child);
virtual void childChanged(SGPropertyNode * child);

View File

@@ -20,6 +20,7 @@
#include <simgear/canvas/Canvas.hxx>
#include <simgear/canvas/CanvasSystemAdapter.hxx>
#include <simgear/scene/util/parse_color.hxx>
#include <osg/Version>
#include <osgText/Text>
namespace simgear
@@ -33,10 +34,13 @@ namespace canvas
TextOSG(canvas::Text* text);
void setFontResolution(int res);
void setCharacterAspect(float aspect);
void setLineHeight(float factor);
void setFill(const std::string& fill);
void setBackgroundColor(const std::string& fill);
SGVec2i sizeForWidth(int w) const;
osg::Vec2 handleHit(const osg::Vec2f& pos);
virtual osg::BoundingBox computeBound() const;
@@ -44,13 +48,21 @@ namespace canvas
protected:
canvas::Text *_text_element;
virtual void computePositions(unsigned int contextID) const;
};
//----------------------------------------------------------------------------
Text::TextOSG::TextOSG(canvas::Text* text):
_text_element(text)
{
setBackdropImplementation(NO_DEPTH_BUFFER);
}
//----------------------------------------------------------------------------
void Text::TextOSG::setFontResolution(int res)
{
TextBase::setFontResolution(res, res);
}
//----------------------------------------------------------------------------
@@ -59,6 +71,12 @@ namespace canvas
setCharacterSize(getCharacterHeight(), aspect);
}
//----------------------------------------------------------------------------
void Text::TextOSG::setLineHeight(float factor)
{
setLineSpacing(factor - 1);
}
//----------------------------------------------------------------------------
void Text::TextOSG::setFill(const std::string& fill)
{
@@ -78,6 +96,281 @@ namespace canvas
setBoundingBoxColor( color );
}
//----------------------------------------------------------------------------
// simplified version of osgText::Text::computeGlyphRepresentation() to
// just calculate the size for a given weight. Glpyh calculations/creating
// is not necessary for this...
SGVec2i Text::TextOSG::sizeForWidth(int w) const
{
if( _text.empty() )
return SGVec2i(0, 0);
osgText::Font* activefont = const_cast<osgText::Font*>(getActiveFont());
if( !activefont )
return SGVec2i(-1, -1);
float max_width_safe = _maximumWidth;
const_cast<TextOSG*>(this)->_maximumWidth = w;
SGRecti bb;
osg::Vec2 startOfLine_coords(0.0f,0.0f);
osg::Vec2 cursor(startOfLine_coords);
osg::Vec2 local(0.0f,0.0f);
unsigned int previous_charcode = 0;
unsigned int line_length = 0;
bool horizontal = _layout != VERTICAL;
bool kerning = true;
float hr = _characterHeight;
float wr = hr / getCharacterAspectRatio();
// osg should really care more about const :-/
osgText::String& text = const_cast<osgText::String&>(_text);
typedef osgText::String::iterator TextIterator;
for( TextIterator itr = text.begin(); itr != text.end(); )
{
// record the start of the current line
TextIterator startOfLine_itr = itr;
// find the end of the current line.
osg::Vec2 endOfLine_coords(cursor);
TextIterator endOfLine_itr =
const_cast<TextOSG*>(this)->computeLastCharacterOnLine(
endOfLine_coords, itr, text.end()
);
line_length = endOfLine_itr - startOfLine_itr;
// Set line position to correct alignment.
switch( _layout )
{
case LEFT_TO_RIGHT:
{
switch( _alignment )
{
// nothing to be done for these
//case LEFT_TOP:
//case LEFT_CENTER:
//case LEFT_BOTTOM:
//case LEFT_BASE_LINE:
//case LEFT_BOTTOM_BASE_LINE:
// break;
case CENTER_TOP:
case CENTER_CENTER:
case CENTER_BOTTOM:
case CENTER_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
cursor.x() = (cursor.x() - endOfLine_coords.x()) * 0.5f;
break;
case RIGHT_TOP:
case RIGHT_CENTER:
case RIGHT_BOTTOM:
case RIGHT_BASE_LINE:
case RIGHT_BOTTOM_BASE_LINE:
cursor.x() = cursor.x() - endOfLine_coords.x();
break;
default:
break;
}
break;
}
case RIGHT_TO_LEFT:
{
switch( _alignment )
{
case LEFT_TOP:
case LEFT_CENTER:
case LEFT_BOTTOM:
case LEFT_BASE_LINE:
case LEFT_BOTTOM_BASE_LINE:
cursor.x() = 2 * cursor.x() - endOfLine_coords.x();
break;
case CENTER_TOP:
case CENTER_CENTER:
case CENTER_BOTTOM:
case CENTER_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
cursor.x() = cursor.x()
+ (cursor.x() - endOfLine_coords.x()) * 0.5f;
break;
// nothing to be done for these
//case RIGHT_TOP:
//case RIGHT_CENTER:
//case RIGHT_BOTTOM:
//case RIGHT_BASE_LINE:
//case RIGHT_BOTTOM_BASE_LINE:
// break;
default:
break;
}
break;
}
case VERTICAL:
{
switch( _alignment )
{
// TODO: current behaviour top baselines lined up in both cases - need to implement
// top of characters alignment - Question is this necessary?
// ... otherwise, nothing to be done for these 6 cases
//case LEFT_TOP:
//case CENTER_TOP:
//case RIGHT_TOP:
// break;
//case LEFT_BASE_LINE:
//case CENTER_BASE_LINE:
//case RIGHT_BASE_LINE:
// break;
case LEFT_CENTER:
case CENTER_CENTER:
case RIGHT_CENTER:
cursor.y() = cursor.y()
+ (cursor.y() - endOfLine_coords.y()) * 0.5f;
break;
case LEFT_BOTTOM_BASE_LINE:
case CENTER_BOTTOM_BASE_LINE:
case RIGHT_BOTTOM_BASE_LINE:
cursor.y() = cursor.y() - (line_length * _characterHeight);
break;
case LEFT_BOTTOM:
case CENTER_BOTTOM:
case RIGHT_BOTTOM:
cursor.y() = 2 * cursor.y() - endOfLine_coords.y();
break;
default:
break;
}
break;
}
}
if( itr != endOfLine_itr )
{
for(;itr != endOfLine_itr;++itr)
{
unsigned int charcode = *itr;
osgText::Glyph* glyph = activefont->getGlyph(_fontSize, charcode);
if( glyph )
{
float width = (float) (glyph->getWidth()) * wr;
float height = (float) (glyph->getHeight()) * hr;
if( _layout == RIGHT_TO_LEFT )
{
cursor.x() -= glyph->getHorizontalAdvance() * wr;
}
// adjust cursor position w.r.t any kerning.
if( kerning && previous_charcode )
{
switch( _layout )
{
case LEFT_TO_RIGHT:
{
osg::Vec2 delta( activefont->getKerning( previous_charcode,
charcode,
_kerningType ) );
cursor.x() += delta.x() * wr;
cursor.y() += delta.y() * hr;
break;
}
case RIGHT_TO_LEFT:
{
osg::Vec2 delta( activefont->getKerning( charcode,
previous_charcode,
_kerningType ) );
cursor.x() -= delta.x() * wr;
cursor.y() -= delta.y() * hr;
break;
}
case VERTICAL:
break; // no kerning when vertical.
}
}
local = cursor;
osg::Vec2 bearing( horizontal ? glyph->getHorizontalBearing()
: glyph->getVerticalBearing() );
local.x() += bearing.x() * wr;
local.y() += bearing.y() * hr;
// set up the coords of the quad
osg::Vec2 upLeft = local + osg::Vec2(0.f, height);
osg::Vec2 lowLeft = local;
osg::Vec2 lowRight = local + osg::Vec2(width, 0.f);
osg::Vec2 upRight = local + osg::Vec2(width, height);
// move the cursor onto the next character.
// also expand bounding box
switch( _layout )
{
case LEFT_TO_RIGHT:
cursor.x() += glyph->getHorizontalAdvance() * wr;
bb.expandBy(lowLeft.x(), lowLeft.y());
bb.expandBy(upRight.x(), upRight.y());
break;
case VERTICAL:
cursor.y() -= glyph->getVerticalAdvance() * hr;
bb.expandBy(upLeft.x(), upLeft.y());
bb.expandBy(lowRight.x(), lowRight.y());
break;
case RIGHT_TO_LEFT:
bb.expandBy(lowRight.x(), lowRight.y());
bb.expandBy(upLeft.x(), upLeft.y());
break;
}
previous_charcode = charcode;
}
}
// skip over spaces and return.
while( itr != text.end() && *itr == ' ' )
++itr;
if( itr != text.end() && *itr == '\n' )
++itr;
}
else
{
++itr;
}
// move to new line.
switch( _layout )
{
case LEFT_TO_RIGHT:
{
startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
cursor = startOfLine_coords;
previous_charcode = 0;
break;
}
case RIGHT_TO_LEFT:
{
startOfLine_coords.y() -= _characterHeight * (1.0 + _lineSpacing);
cursor = startOfLine_coords;
previous_charcode = 0;
break;
}
case VERTICAL:
{
startOfLine_coords.x() += _characterHeight * (1.0 + _lineSpacing)
/ getCharacterAspectRatio();
cursor = startOfLine_coords;
previous_charcode = 0;
break;
}
}
}
const_cast<TextOSG*>(this)->_maximumWidth = max_width_safe;
return bb.size();
}
//----------------------------------------------------------------------------
osg::Vec2 Text::TextOSG::handleHit(const osg::Vec2f& pos)
{
@@ -162,19 +455,120 @@ namespace canvas
osg::BoundingBox Text::TextOSG::computeBound() const
{
osg::BoundingBox bb = osgText::Text::computeBound();
if( !bb.valid() )
return bb;
// TODO bounding box still doesn't seem always right (eg. with center
// horizontal alignment not completely accurate)
bb._min.y() += _offset.y();
bb._max.y() += _offset.y();
_text_element->setBoundingBox(bb);
#if OSG_VERSION_LESS_THAN(3,1,0)
if( bb.valid() )
{
// TODO bounding box still doesn't seem always right (eg. with center
// horizontal alignment not completely accurate)
bb._min.y() += _offset.y();
bb._max.y() += _offset.y();
}
#endif
return bb;
}
//----------------------------------------------------------------------------
void Text::TextOSG::computePositions(unsigned int contextID) const
{
if( _textureGlyphQuadMap.empty() || _layout == VERTICAL )
return osgText::Text::computePositions(contextID);
// TODO check when it can be larger
assert( _textureGlyphQuadMap.size() == 1 );
const GlyphQuads& quads = _textureGlyphQuadMap.begin()->second;
const GlyphQuads::Glyphs& glyphs = quads._glyphs;
const GlyphQuads::Coords2& coords = quads._coords;
const GlyphQuads::LineNumbers& line_numbers = quads._lineNumbers;
float wr = _characterHeight / getCharacterAspectRatio();
size_t cur_line = static_cast<size_t>(-1);
for(size_t i = 0; i < glyphs.size(); ++i)
{
// Check horizontal offsets
bool first_char = cur_line != line_numbers[i];
cur_line = line_numbers[i];
bool last_char = (i + 1 == glyphs.size())
|| (cur_line != line_numbers[i + 1]);
if( first_char || last_char )
{
// From osg/src/osgText/Text.cpp:
//
// osg::Vec2 upLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
// osg::Vec2 lowLeft = local+osg::Vec2(0.0f-fHorizQuadMargin, ...);
// osg::Vec2 lowRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
// osg::Vec2 upRight = local+osg::Vec2(width+fHorizQuadMargin, ...);
float left = coords[i * 4].x(),
right = coords[i * 4 + 2].x(),
width = glyphs[i]->getWidth() * wr;
// (local + width + fHoriz) - (local - fHoriz) = width + 2*fHoriz | -width
float margin = 0.5f * (right - left - width),
cursor_x = left + margin
- glyphs[i]->getHorizontalBearing().x() * wr;
if( first_char )
{
if( cur_line == 0 || cursor_x < _textBB._min.x() )
_textBB._min.x() = cursor_x;
}
if( last_char )
{
float cursor_w = cursor_x + glyphs[i]->getHorizontalAdvance() * wr;
if( cur_line == 0 || cursor_w > _textBB._max.x() )
_textBB._max.x() = cursor_w;
}
}
}
return osgText::Text::computePositions(contextID);
}
//----------------------------------------------------------------------------
const std::string Text::TYPE_NAME = "text";
//----------------------------------------------------------------------------
void Text::staticInit()
{
if( isInit<Text>() )
return;
osg::ref_ptr<TextOSG> Text::*text = &Text::_text;
addStyle("fill", "color", &TextOSG::setFill, text);
addStyle("background", "color", &TextOSG::setBackgroundColor, text);
addStyle("character-size",
"numeric",
static_cast<
void (TextOSG::*)(float)
> (&TextOSG::setCharacterSize),
text);
addStyle("character-aspect-ratio",
"numeric",
&TextOSG::setCharacterAspect, text);
addStyle("line-height", "numeric", &TextOSG::setLineHeight, text);
addStyle("font-resolution", "numeric", &TextOSG::setFontResolution, text);
addStyle("padding", "numeric", &TextOSG::setBoundingBoxMargin, text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", "", &TextOSG::setDrawMode, text);
addStyle("max-width", "numeric", &TextOSG::setMaximumWidth, text);
addStyle("font", "", &Text::setFont);
addStyle("alignment", "", &Text::setAlignment);
addStyle("text", "", &Text::setText, false);
}
//----------------------------------------------------------------------------
Text::Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
@@ -183,28 +577,13 @@ namespace canvas
Element(canvas, node, parent_style, parent),
_text( new Text::TextOSG(this) )
{
staticInit();
setDrawable(_text);
_text->setCharacterSizeMode(osgText::Text::OBJECT_COORDS);
_text->setAxisAlignment(osgText::Text::USER_DEFINED_ROTATION);
_text->setRotation(osg::Quat(osg::PI, osg::X_AXIS));
addStyle("fill", &TextOSG::setFill, _text);
addStyle("background", &TextOSG::setBackgroundColor, _text);
addStyle("character-size",
static_cast<void (TextOSG::*)(float)>(&TextOSG::setCharacterSize),
_text);
addStyle("character-aspect-ratio", &TextOSG::setCharacterAspect, _text);
addStyle("padding", &TextOSG::setBoundingBoxMargin, _text);
// TEXT = 1 default
// BOUNDINGBOX = 2
// FILLEDBOUNDINGBOX = 4
// ALIGNMENT = 8
addStyle<int>("draw-mode", &TextOSG::setDrawMode, _text);
addStyle("max-width", &TextOSG::setMaximumWidth, _text);
addStyle("font", &Text::setFont, this);
addStyle("alignment", &Text::setAlignment, this);
addStyle("text", &Text::setText, this);
setupStyle();
}
@@ -223,7 +602,7 @@ namespace canvas
//----------------------------------------------------------------------------
void Text::setFont(const char* name)
{
_text->setFont( _canvas.lock()->getSystemAdapter()->getFont(name) );
_text->setFont( Canvas::getSystemAdapter()->getFont(name) );
}
//----------------------------------------------------------------------------
@@ -266,6 +645,18 @@ namespace canvas
}
#endif
//----------------------------------------------------------------------------
int Text::heightForWidth(int w) const
{
return _text->sizeForWidth(w).y();
}
//----------------------------------------------------------------------------
int Text::maxWidth() const
{
return _text->sizeForWidth(INT_MAX).x();
}
//----------------------------------------------------------------------------
osg::Vec2 Text::getNearestCursor(const osg::Vec2& pos) const
{

View File

@@ -33,6 +33,9 @@ namespace canvas
public Element
{
public:
static const std::string TYPE_NAME;
static void staticInit();
Text( const CanvasWeakPtr& canvas,
const SGPropertyNode_ptr& node,
const Style& parent_style,
@@ -43,6 +46,8 @@ namespace canvas
void setFont(const char* name);
void setAlignment(const char* align);
int heightForWidth(int w) const;
int maxWidth() const;
osg::Vec2 getNearestCursor(const osg::Vec2& pos) const;
protected:

View File

@@ -0,0 +1,66 @@
/// Unit tests for canvas::Element
#define BOOST_TEST_MODULE canvas
#include <BoostTestTargetConfig.h>
#include "CanvasElement.hxx"
#include "CanvasGroup.hxx"
namespace sc = simgear::canvas;
BOOST_AUTO_TEST_CASE( attr_data )
{
// http://www.w3.org/TR/html5/dom.html#attr-data-*
#define SG_CHECK_ATTR2PROP(attr, prop)\
BOOST_CHECK_EQUAL(sc::Element::attrToDataPropName(attr), prop)
// If name starts with "data-", for each "-" (U+002D) character in the name
// that is followed by a lowercase ASCII letter, remove the "-" (U+002D)
// character and replace the character that followed it by the same character
// converted to ASCII uppercase.
SG_CHECK_ATTR2PROP("no-data", "");
SG_CHECK_ATTR2PROP("data-blub", "blub");
SG_CHECK_ATTR2PROP("data-blub-x-y", "blubXY");
SG_CHECK_ATTR2PROP("data-blub-x-y-", "blubXY-");
#undef SG_CHECK_ATTR2PROP
#define SG_CHECK_PROP2ATTR(prop, attr)\
BOOST_CHECK_EQUAL(sc::Element::dataPropToAttrName(prop), attr)
// If name contains a "-" (U+002D) character followed by a lowercase ASCII
// letter, throw a SyntaxError exception (empty string) and abort these steps.
// For each uppercase ASCII letter in name, insert a "-" (U+002D) character
// before the character and replace the character with the same character
// converted to ASCII lowercase.
// Insert the string "data-" at the front of name.
SG_CHECK_PROP2ATTR("test", "data-test");
SG_CHECK_PROP2ATTR("testIt", "data-test-it");
SG_CHECK_PROP2ATTR("testIt-Hyphen", "data-test-it--hyphen");
SG_CHECK_PROP2ATTR("-test", "");
SG_CHECK_PROP2ATTR("test-", "data-test-");
#undef SG_CHECK_PROP2ATTR
SGPropertyNode_ptr node = new SGPropertyNode;
sc::ElementPtr el =
sc::Element::create<sc::Group>(sc::CanvasWeakPtr(), node);
el->setDataProp("myData", 3);
BOOST_CHECK_EQUAL( el->getDataProp<int>("myData"), 3 );
BOOST_CHECK_EQUAL( node->getIntValue("data-my-data"), 3 );
SGPropertyNode* prop = el->getDataProp<SGPropertyNode*>("notExistingProp");
BOOST_CHECK( !prop );
prop = el->getDataProp<SGPropertyNode*>("myData");
BOOST_CHECK( prop );
BOOST_CHECK_EQUAL( prop->getParent(), node );
BOOST_CHECK_EQUAL( prop->getIntValue(), 3 );
BOOST_CHECK( el->hasDataProp("myData") );
el->removeDataProp("myData");
BOOST_CHECK( !el->hasDataProp("myData") );
BOOST_CHECK_EQUAL( el->getDataProp("myData", 5), 5 );
}

View File

@@ -0,0 +1,21 @@
#ifndef CANVAS_PATH_HXX_
# error Canvas - do not include this file!
#endif
#define n BOOST_PP_ITERATION()
Path& addSegment( uint8_t cmd
BOOST_PP_COMMA_IF(n)
BOOST_PP_ENUM_PARAMS(n, float coord) )
{
_node->addChild("cmd")->setIntValue(cmd);
#define SG_CANVAS_PATH_SET_COORD(z, n, dummy)\
_node->addChild("coord")->setFloatValue(coord##n);
BOOST_PP_REPEAT(n, SG_CANVAS_PATH_SET_COORD, 0)
#undef SG_CANVAS_PATH_SET_COORD
return *this;
}
#undef n

View File

@@ -0,0 +1,18 @@
include (SimGearComponent)
set(HEADERS
CustomEvent.hxx
MouseEvent.hxx
)
set(SOURCES
CustomEvent.cxx
MouseEvent.cxx
)
simgear_scene_component(canvas-events canvas/events "${SOURCES}" "${HEADERS}")
add_boost_test(canvas_event
SOURCES event_test.cpp
LIBRARIES ${TEST_LIBS}
)

View File

@@ -0,0 +1,55 @@
// Canvas user defined event
//
// 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 "CustomEvent.hxx"
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
CustomEvent::CustomEvent( std::string const& type_str,
bool bubbles,
StringMap const& data ):
detail(data),
bubbles(bubbles)
{
type = getOrRegisterType(type_str);
}
//----------------------------------------------------------------------------
CustomEvent::CustomEvent( int type_id,
bool bubbles,
StringMap const& data ):
detail(data),
bubbles(bubbles)
{
type = type_id;
// TypeMap::map_by<id>::type const& type_map = getTypeMap().by<id>();
// assert( type_map.find(type_id) != type_map.end() );
}
//----------------------------------------------------------------------------
void CustomEvent::setDetail(StringMap const& data)
{
detail = data;
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,73 @@
///@file Canvas user defined event
//
// 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 CANVAS_CUSTOM_EVENT_HXX_
#define CANVAS_CUSTOM_EVENT_HXX_
#include <simgear/canvas/CanvasEvent.hxx>
#include <simgear/structure/map.hxx>
namespace simgear
{
namespace canvas
{
class CustomEvent:
public Event
{
public:
/**
*
* @param type_str Event type name (if name does not exist yet it will
* be registered as new event type)
* @param data Optional user data stored in event
*/
CustomEvent( std::string const& type_str,
bool bubbles = false,
StringMap const& data = StringMap() );
/**
*
* @param type_id Event type id
* @param data Optional user data stored in event
*/
CustomEvent( int type_id,
bool bubbles = false,
StringMap const& data = StringMap() );
/**
* Set user data
*/
void setDetail(StringMap const& data);
/**
* Get user data
*/
StringMap const& getDetail() const { return detail; }
virtual bool canBubble() const { return bubbles; }
StringMap detail; //<! user data map
bool bubbles;
};
} // namespace canvas
} // namespace simgear
#endif /* CANVAS_CUSTOM_EVENT_HXX_ */

View File

@@ -1,6 +1,6 @@
// Listener for canvas (GUI) events being passed to a Nasal function/code
// Mouse event
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
// 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
@@ -16,11 +16,7 @@
// 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 "CanvasEvent.hxx"
#include "CanvasEventListener.hxx"
#include "CanvasSystemAdapter.hxx"
#include <simgear/nasal/cppbind/Ghost.hxx>
#include "MouseEvent.hxx"
namespace simgear
{
@@ -28,43 +24,47 @@ namespace canvas
{
//----------------------------------------------------------------------------
EventListener::EventListener(naRef code, const SystemAdapterPtr& sys_adapter):
_code(code),
_gc_key(-1),
_sys(sys_adapter)
MouseEvent::MouseEvent():
button(0),
buttons(0),
modifiers(0),
click_count(0)
{
assert( sys_adapter );
if( !naIsCode(code)
&& !naIsCCode(code)
&& !naIsFunc(code) )
throw std::runtime_error
(
"canvas::EventListener: invalid function argument"
);
_gc_key = sys_adapter->gcSave(_code);
}
//----------------------------------------------------------------------------
EventListener::~EventListener()
MouseEvent::MouseEvent(const osgGA::GUIEventAdapter& ea):
button(0),
buttons(ea.getButtonMask()),
modifiers(ea.getModKeyMask()),
click_count(0)
{
assert( !_sys.expired() );
_sys.lock()->gcRelease(_gc_key);
time = ea.getTime();
// Convert button mask to index
int button_mask = ea.getButton();
while( (button_mask >>= 1) > 0 )
button += 1;
}
//----------------------------------------------------------------------------
void EventListener::call(const canvas::EventPtr& event)
bool MouseEvent::canBubble() const
{
SystemAdapterPtr sys = _sys.lock();
naRef args[] = {
nasal::Ghost<EventPtr>::create(sys->getNasalContext(), event)
// Check if event supports bubbling
const Event::Type types_no_bubbling[] = {
Event::MOUSE_ENTER,
Event::MOUSE_LEAVE,
};
const int num_args = sizeof(args)/sizeof(args[0]);
const size_t num_types_no_bubbling = sizeof(types_no_bubbling)
/ sizeof(types_no_bubbling[0]);
sys->callMethod(_code, naNil(), num_args, args, naNil());
for( size_t i = 0; i < num_types_no_bubbling; ++i )
if( type == types_no_bubbling[i] )
return false;
return true;
}
} // namespace canvas
} // namespace simgear

View File

@@ -1,4 +1,4 @@
// Mouse event
///@file Mouse event
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
//
@@ -19,7 +19,7 @@
#ifndef CANVAS_MOUSE_EVENT_HXX_
#define CANVAS_MOUSE_EVENT_HXX_
#include "CanvasEvent.hxx"
#include <simgear/canvas/CanvasEvent.hxx>
#include <osgGA/GUIEventAdapter>
namespace simgear
@@ -31,15 +31,14 @@ namespace canvas
public Event
{
public:
MouseEvent():
button(-1),
state(-1),
mod(-1),
click_count(0)
{}
MouseEvent();
MouseEvent(const osgGA::GUIEventAdapter& ea);
virtual bool canBubble() const;
osg::Vec2f getScreenPos() const { return screen_pos; }
osg::Vec2f getClientPos() const { return client_pos; }
osg::Vec2f getLocalPos() const { return local_pos; }
osg::Vec2f getDelta() const { return delta; }
float getScreenX() const { return screen_pos.x(); }
@@ -48,17 +47,25 @@ namespace canvas
float getClientX() const { return client_pos.x(); }
float getClientY() const { return client_pos.y(); }
float getLocalX() const { return local_pos.x(); }
float getLocalY() const { return local_pos.y(); }
float getDeltaX() const { return delta.x(); }
float getDeltaY() const { return delta.y(); }
int getButton() const { return button; }
int getButtonMask() const { return buttons; }
int getModifiers() const { return modifiers; }
int getCurrentClickCount() const { return click_count; }
osg::Vec2f screen_pos, //<! Position in screen coordinates
client_pos, //<! Position in window/canvas coordinates
local_pos, //<! Position in local/element coordinates
delta;
int button, //<! Button for this event
state, //<! Current button state
mod, //<! Keyboard modifier state
buttons, //<! Current button state
modifiers, //<! Keyboard modifier state
click_count; //<! Current click count
};

View File

@@ -0,0 +1,36 @@
/// Unit tests for reference counting and smart pointer classes
#define BOOST_TEST_MODULE structure
#include <BoostTestTargetConfig.h>
#include "MouseEvent.hxx"
#include "CustomEvent.hxx"
namespace sc = simgear::canvas;
BOOST_AUTO_TEST_CASE( canvas_event_types )
{
// Register type
BOOST_REQUIRE_EQUAL( sc::Event::strToType("test"),
sc::Event::UNKNOWN );
BOOST_REQUIRE_EQUAL( sc::Event::getOrRegisterType("test"),
sc::Event::CUSTOM_EVENT );
BOOST_REQUIRE_EQUAL( sc::Event::strToType("test"),
sc::Event::CUSTOM_EVENT );
BOOST_REQUIRE_EQUAL( sc::Event::typeToStr(sc::Event::CUSTOM_EVENT),
"test" );
// Basic internal type
BOOST_REQUIRE_EQUAL( sc::Event::typeToStr(sc::Event::MOUSE_DOWN),
"mousedown" );
BOOST_REQUIRE_EQUAL( sc::Event::strToType("mousedown"),
sc::Event::MOUSE_DOWN );
// Unknown type
BOOST_REQUIRE_EQUAL( sc::Event::typeToStr(123),
"unknown" );
// Register type through custom event instance
sc::CustomEvent e("blub");
BOOST_REQUIRE_EQUAL( e.getTypeString(), "blub" );
BOOST_REQUIRE_NE( e.getType(), sc::Event::UNKNOWN );
}

View File

@@ -0,0 +1,488 @@
// 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 "SpacerItem.hxx"
#include <simgear/canvas/Canvas.hxx>
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
BoxLayout::BoxLayout(Direction dir):
_padding(5)
{
setDirection(dir);
}
//----------------------------------------------------------------------------
BoxLayout::~BoxLayout()
{
_parent.reset(); // No need to invalidate parent again...
clear();
}
//----------------------------------------------------------------------------
void BoxLayout::addItem(const LayoutItemRef& item)
{
return addItem(item, 0);
}
//----------------------------------------------------------------------------
void BoxLayout::addItem(const LayoutItemRef& item, int stretch)
{
insertItem(-1, item, stretch);
}
//----------------------------------------------------------------------------
void BoxLayout::addStretch(int stretch)
{
insertStretch(-1, stretch);
}
//----------------------------------------------------------------------------
void BoxLayout::addSpacing(int size)
{
insertSpacing(-1, size);
}
//----------------------------------------------------------------------------
void BoxLayout::insertItem(int index, 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);
if( index < 0 )
_layout_items.push_back(item_data);
else
_layout_items.insert(_layout_items.begin() + index, item_data);
invalidate();
}
//----------------------------------------------------------------------------
void BoxLayout::insertStretch(int index, int stretch)
{
insertItem(index, LayoutItemRef(new SpacerItem()), stretch);
}
//----------------------------------------------------------------------------
void BoxLayout::insertSpacing(int index, int size)
{
SGVec2i size_hint = horiz()
? SGVec2i(size, 0)
: SGVec2i(0, size),
max_size = size_hint;
insertItem(index, LayoutItemRef(new SpacerItem(size_hint, max_size)));
}
//----------------------------------------------------------------------------
size_t BoxLayout::count() const
{
return _layout_items.size();
}
//----------------------------------------------------------------------------
LayoutItemRef BoxLayout::itemAt(size_t index)
{
if( index >= _layout_items.size() )
return LayoutItemRef();
return _layout_items[index].layout_item;
}
//----------------------------------------------------------------------------
LayoutItemRef BoxLayout::takeAt(size_t index)
{
if( index >= _layout_items.size() )
return LayoutItemRef();
LayoutItems::iterator it = _layout_items.begin() + index;
LayoutItemRef item = it->layout_item;
item->onRemove();
_layout_items.erase(it);
invalidate();
return item;
}
//----------------------------------------------------------------------------
void BoxLayout::clear()
{
for( LayoutItems::iterator it = _layout_items.begin();
it != _layout_items.end();
++it )
{
it->layout_item->onRemove();
}
_layout_items.clear();
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();
}
//----------------------------------------------------------------------------
bool BoxLayout::setStretchFactor(const LayoutItemRef& item, int stretch)
{
for( LayoutItems::iterator it = _layout_items.begin();
it != _layout_items.end();
++it )
{
if( item == it->layout_item )
{
it->stretch = std::max(0, stretch);
invalidate();
return true;
}
}
return false;
}
//----------------------------------------------------------------------------
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)
{
_direction = dir;
_get_layout_coord = &SGVec2i::x;
_get_fixed_coord = &SGVec2i::y;
if( !horiz() )
std::swap(_get_layout_coord, _get_fixed_coord);
invalidate();
}
//----------------------------------------------------------------------------
BoxLayout::Direction BoxLayout::direction() const
{
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)
{
_canvas = canvas;
for(size_t i = 0; i < _layout_items.size(); ++i)
_layout_items[i].layout_item->setCanvas(canvas);
}
//----------------------------------------------------------------------------
bool BoxLayout::horiz() const
{
return (_direction == LeftToRight) || (_direction == RightToLeft);
}
//----------------------------------------------------------------------------
void BoxLayout::updateSizeHints() const
{
SGVec2i min_size(0, 0),
max_size(0, 0),
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)
{
// 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)();
item_data.has_hfw = item.hasHeightForWidth();
if( !dynamic_cast<SpacerItem*>(item_data.layout_item.get()) )
{
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( 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);
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;
}
//----------------------------------------------------------------------------
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
{
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();
// 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() );
data.min_size = data.mhfw(w);
data.size_hint = data.hfw(w);
// Update size hints for layouting with difference to size hints
// calculated by using the size hints provided (without trading
// height for width)
_layout_data.min_size += data.min_size
- data.layout_item->minimumSize().y();
_layout_data.size_hint += data.size_hint
- data.layout_item->sizeHint().y();
}
}
}
// 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)() );
bool reverse = (_direction == RightToLeft) || (_direction == BottomToTop);
if( reverse )
cur_pos.x() += (geom.size().*_get_layout_coord)();
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,154 @@
// 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);
~BoxLayout();
virtual void addItem(const LayoutItemRef& item);
void addItem(const LayoutItemRef& item, int stretch);
void addStretch(int stretch = 0);
void addSpacing(int size);
void insertItem(int index, const LayoutItemRef& item, int stretch = 0);
void insertStretch(int index, int stretch = 0);
void insertSpacing(int index, int size);
virtual size_t count() const;
virtual LayoutItemRef itemAt(size_t index);
virtual LayoutItemRef takeAt(size_t index);
virtual void clear();
/**
* Set the stretch factor of the item at position @a index to @a stretch.
*/
void setStretch(size_t index, int stretch);
/**
* Set the stretch factor of the given @a item to @a stretch, if it exists
* in this layout.
*
* @return true, if the @a item was found in the layout
*/
bool setStretchFactor(const LayoutItemRef& item, 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 bool hasHeightForWidth() const;
virtual int heightForWidth(int w) const;
virtual int minimumHeightForWidth(int w) const;
virtual void setCanvas(const CanvasWeakPtr& canvas);
bool horiz() const;
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;
Direction _direction;
typedef std::vector<ItemData> LayoutItems;
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;
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();
};
typedef SGSharedPtr<BoxLayout> BoxLayoutRef;
} // namespace canvas
} // namespace simgear
#endif /* SG_CANVAS_BOX_LAYOUT_HXX_ */

View File

@@ -0,0 +1,24 @@
include (SimGearComponent)
set(HEADERS
BoxLayout.hxx
Layout.hxx
LayoutItem.hxx
NasalWidget.hxx
SpacerItem.hxx
)
set(SOURCES
BoxLayout.cxx
Layout.cxx
LayoutItem.cxx
NasalWidget.cxx
SpacerItem.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,295 @@
// 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 )
{
_geometry = geom;
_flags |= LAYOUT_DIRTY;
}
update();
}
//----------------------------------------------------------------------------
void Layout::removeItem(const LayoutItemRef& item)
{
size_t i = 0;
while( LayoutItemRef child = itemAt(i) )
{
if( item == child )
return (void)takeAt(i);
++i;
}
}
//----------------------------------------------------------------------------
void Layout::clear()
{
while( itemAt(0) )
takeAt(0);
}
//----------------------------------------------------------------------------
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;
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)
{
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(" << space.size << "px for "
<< num_children << " items, s.t."
<< " min=" << space.min_size
<< ", hint=" << space.size_hint
<< ", max=" << space.max_size << ")" );
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);
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;
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,133 @@
// 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;
/**
* Get the number of items.
*/
virtual size_t count() const = 0;
/**
* Get the item at position @a index.
*
* If there is no such item the function must do nothing and return an
* empty reference.
*/
virtual LayoutItemRef itemAt(size_t index) = 0;
/**
* Remove and get the item at position @a index.
*
* If there is no such item the function must do nothing and return an
* empty reference.
*/
virtual LayoutItemRef takeAt(size_t index) = 0;
/**
* Remove the given @a item from the layout.
*/
void removeItem(const LayoutItemRef& item);
/**
* Remove all items.
*/
virtual void clear();
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 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;
};
/**
* 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,171 @@
// 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
{
const SGVec2i LayoutItem::MAX_SIZE( SGLimits<int>::max(),
SGLimits<int>::max() );
//----------------------------------------------------------------------------
LayoutItem::LayoutItem():
_flags(0),
_size_hint(0, 0),
_min_size(0, 0),
_max_size(MAX_SIZE)
{
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;
}
//----------------------------------------------------------------------------
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()
{
_flags |= SIZE_INFO_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 _size_hint;
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::minimumSizeImpl() const
{
return _min_size;
}
//----------------------------------------------------------------------------
SGVec2i LayoutItem::maximumSizeImpl() const
{
return _max_size;
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,149 @@
///@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:
/** Maximum item size (indicating no limit) */
static const SGVec2i MAX_SIZE;
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;
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.
*/
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 (usually this is a layout).
*/
void setParent(const LayoutItemWeakRef& parent);
/**
* Get the parent layout.
*/
LayoutItemRef getParent() const;
/// Called before item is removed from a layout
virtual void onRemove() {}
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,295 @@
// 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):
Object(impl),
_layout_size_hint(32, 32),
_layout_min_size(16, 16),
_layout_max_size(MAX_SIZE),
_user_size_hint(0, 0),
_user_min_size(0, 0),
_user_max_size(MAX_SIZE)
{
}
//----------------------------------------------------------------------------
NasalWidget::~NasalWidget()
{
onRemove();
}
//----------------------------------------------------------------------------
void NasalWidget::invalidate()
{
LayoutItem::invalidate();
_flags |= LAYOUT_DIRTY;
}
//----------------------------------------------------------------------------
void NasalWidget::setGeometry(const SGRect<int>& geom)
{
if( _geometry != geom )
_geometry = geom;
else if( !(_flags & LAYOUT_DIRTY) || !_set_geometry )
return;
naContext c = naNewContext();
try
{
_set_geometry(nasal::to_nasal(c, this), geom);
_flags &= ~LAYOUT_DIRTY;
}
catch( std::exception const& ex )
{
SG_LOG(
SG_GUI,
SG_WARN,
"NasalWidget::setGeometry: callback error: '" << ex.what() << "'"
);
}
naFreeContext(c);
}
//----------------------------------------------------------------------------
void NasalWidget::onRemove()
{
if( !_nasal_impl.valid() )
return;
typedef boost::function<void(nasal::Me)> Deleter;
naContext c = naNewContext();
try
{
Deleter del =
nasal::get_member<Deleter>(c, _nasal_impl.get_naRef(), "onRemove");
if( del )
del(nasal::to_nasal(c, this));
}
catch( std::exception const& ex )
{
SG_LOG(
SG_GUI,
SG_WARN,
"NasalWidget::onRemove: callback error: '" << ex.what() << "'"
);
}
naFreeContext(c);
}
//----------------------------------------------------------------------------
void NasalWidget::setSetGeometryFunc(const SetGeometryFunc& func)
{
_set_geometry = func;
}
//----------------------------------------------------------------------------
void NasalWidget::setHeightForWidthFunc(const HeightForWidthFunc& func)
{
_height_for_width = func;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setMinimumHeightForWidthFunc(const HeightForWidthFunc& func)
{
_min_height_for_width = func;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setSizeHint(const SGVec2i& s)
{
if( _user_size_hint == s )
return;
_user_size_hint = s;
// TODO just invalidate size_hint? Probably not a performance issue...
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setMinimumSize(const SGVec2i& s)
{
if( _user_min_size == s )
return;
_user_min_size = s;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setMaximumSize(const SGVec2i& s)
{
if( _user_max_size == s )
return;
_user_max_size = s;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setLayoutSizeHint(const SGVec2i& s)
{
if( _layout_size_hint == s )
return;
_layout_size_hint = s;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setLayoutMinimumSize(const SGVec2i& s)
{
if( _layout_min_size == s )
return;
_layout_min_size = s;
invalidate();
}
//----------------------------------------------------------------------------
void NasalWidget::setLayoutMaximumSize(const SGVec2i& s)
{
if( _layout_max_size == s )
return;
_layout_max_size = s;
invalidate();
}
//----------------------------------------------------------------------------
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)
{
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>()
.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)
.method("setLayoutSizeHint", &NasalWidget::setLayoutSizeHint)
.method("setLayoutMinimumSize", &NasalWidget::setLayoutMinimumSize)
.method("setLayoutMaximumSize", &NasalWidget::setLayoutMaximumSize);
nasal::Hash widget_hash = ns.createHash("Widget");
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
{
return SGVec2i(
_user_size_hint.x() > 0 ? _user_size_hint.x() : _layout_size_hint.x(),
_user_size_hint.y() > 0 ? _user_size_hint.y() : _layout_size_hint.y()
);
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::minimumSizeImpl() const
{
return SGVec2i(
_user_min_size.x() > 0 ? _user_min_size.x() : _layout_min_size.x(),
_user_min_size.y() > 0 ? _user_min_size.y() : _layout_min_size.y()
);
}
//----------------------------------------------------------------------------
SGVec2i NasalWidget::maximumSizeImpl() const
{
return SGVec2i(
_user_max_size.x() < MAX_SIZE.x() ? _user_max_size.x()
: _layout_max_size.x(),
_user_max_size.y() < MAX_SIZE.y() ? _user_max_size.y()
: _layout_max_size.y()
);
}
} // namespace canvas
} // namespace simgear

View File

@@ -0,0 +1,129 @@
///@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>
#include <simgear/nasal/cppbind/NasalObject.hxx>
namespace simgear
{
namespace canvas
{
/**
* Baseclass/ghost to create widgets with Nasal.
*/
class NasalWidget:
public LayoutItem,
public nasal::Object
{
public:
typedef boost::function<void (nasal::Me, const SGRecti&)> SetGeometryFunc;
typedef boost::function<int (nasal::Me, int)> HeightForWidthFunc;
/**
*
* @param impl Initial implementation hash (nasal part of
* implementation)
*/
NasalWidget(naRef impl);
~NasalWidget();
virtual void invalidate();
virtual void setGeometry(const SGRecti& geom);
virtual void onRemove();
void setSetGeometryFunc(const SetGeometryFunc& func);
void setHeightForWidthFunc(const HeightForWidthFunc& func);
void setMinimumHeightForWidthFunc(const HeightForWidthFunc& func);
/** Set size hint.
*
* Overrides default size hint. Set to (0, 0) to fall back to default size
* hint.
*/
void setSizeHint(const SGVec2i& s);
/** Set minimum size.
*
* Overrides default minimum size. Set to (0, 0) to fall back to default
* minimum size.
*/
void setMinimumSize(const SGVec2i& s);
/** Set maximum size.
*
* Overrides default maximum size hint. Set to LayoutItem::MAX_SIZE to
* fall back to default maximum size.
*/
void setMaximumSize(const SGVec2i& s);
void setLayoutSizeHint(const SGVec2i& s);
void setLayoutMinimumSize(const SGVec2i& s);
void setLayoutMaximumSize(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
*/
static void setupGhost(nasal::Hash& ns);
protected:
enum WidgetFlags
{
LAYOUT_DIRTY = LayoutItem::LAST_FLAG << 1,
LAST_FLAG = LAYOUT_DIRTY
};
SetGeometryFunc _set_geometry;
HeightForWidthFunc _height_for_width,
_min_height_for_width;
SGVec2i _layout_size_hint,
_layout_min_size,
_layout_max_size,
_user_size_hint,
_user_min_size,
_user_max_size;
int callHeightForWidthFunc( const HeightForWidthFunc& hfw,
int w ) const;
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,36 @@
// Element providing blank space in a layout.
//
// 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 "SpacerItem.hxx"
namespace simgear
{
namespace canvas
{
//----------------------------------------------------------------------------
SpacerItem::SpacerItem( const SGVec2i& size,
const SGVec2i& max_size )
{
_size_hint = size;
_min_size = size;
_max_size = max_size;
}
} // namespace canvas
} // namespace simgear

View File

@@ -1,6 +1,6 @@
// Listener for canvas (GUI) events being passed to a Nasal function/code
///@file Element providing blank space in a layout.
//
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
// 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
@@ -16,33 +16,28 @@
// 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 CANVAS_EVENT_LISTENER_HXX_
#define CANVAS_EVENT_LISTENER_HXX_
#ifndef SG_CANVAS_SPACER_ITEM_HXX_
#define SG_CANVAS_SPACER_ITEM_HXX_
#include "canvas_fwd.hxx"
#include <simgear/nasal/naref.h>
#include "LayoutItem.hxx"
namespace simgear
{
namespace canvas
{
class EventListener
/**
* Element for providing blank space in a layout.
*/
class SpacerItem:
public LayoutItem
{
public:
EventListener( naRef code,
const SystemAdapterPtr& sys_adapter );
~EventListener();
SpacerItem( const SGVec2i& size = SGVec2i(0, 0),
const SGVec2i& max_size = MAX_SIZE );
void call(const canvas::EventPtr& event);
protected:
naRef _code;
int _gc_key;
SystemAdapterWeakPtr _sys;
};
} // namespace canvas
} // namespace simgear
#endif /* CANVAS_EVENT_LISTENER_HXX_ */
#endif /* SG_CANVAS_SPACER_ITEM_HXX_ */

View File

@@ -0,0 +1,461 @@
// 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 = 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; }
};
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;
//------------------------------------------------------------------------------
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_REQUIRE( hbox.setStretchFactor(w1, 2) );
BOOST_REQUIRE( hbox.setStretchFactor(w2, 3) );
BOOST_CHECK_EQUAL(hbox.stretch(0), 2);
BOOST_CHECK_EQUAL(hbox.stretch(1), 3);
hbox.removeItem(w1);
BOOST_CHECK( !hbox.setStretchFactor(w1, 0) );
}
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( spacer_layouting )
{
sc::HBoxLayout hbox;
TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(9999, 9999) ) ),
w2( new TestWidget(*w1) );
hbox.addItem(w1);
hbox.addItem(w2);
hbox.addStretch(1);
BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(37, 16));
BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(69, 32));
BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
hbox.setGeometry(SGRecti(0, 0, 256, 40));
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(0, 0, 32, 40));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(37, 0, 32, 40));
// now center with increased spacing between both widgets
hbox.insertStretch(0, 1);
hbox.insertSpacing(2, 10);
BOOST_CHECK_EQUAL(hbox.minimumSize(), SGVec2i(47, 16));
BOOST_CHECK_EQUAL(hbox.sizeHint(), SGVec2i(79, 32));
BOOST_CHECK_EQUAL(hbox.maximumSize(), sc::LayoutItem::MAX_SIZE);
hbox.update();
BOOST_CHECK_EQUAL(w1->geometry(), SGRecti(88, 0, 32, 40));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(135, 0, 32, 40));
}
//------------------------------------------------------------------------------
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( boxlayout_insert_remove )
{
sc::HBoxLayout hbox;
BOOST_CHECK_EQUAL(hbox.count(), 0);
BOOST_CHECK(!hbox.itemAt(0));
BOOST_CHECK(!hbox.takeAt(0));
TestWidgetRef w1( new TestWidget( SGVec2i(16, 16),
SGVec2i(32, 32),
SGVec2i(9999, 32) ) ),
w2( new TestWidget(*w1) );
hbox.addItem(w1);
BOOST_CHECK_EQUAL(hbox.count(), 1);
BOOST_CHECK_EQUAL(hbox.itemAt(0), w1);
hbox.insertItem(0, w2);
BOOST_CHECK_EQUAL(hbox.count(), 2);
BOOST_CHECK_EQUAL(hbox.itemAt(0), w2);
BOOST_CHECK_EQUAL(hbox.itemAt(1), w1);
hbox.removeItem(w2);
BOOST_CHECK_EQUAL(hbox.count(), 1);
BOOST_CHECK_EQUAL(hbox.itemAt(0), w1);
hbox.addItem(w2);
BOOST_CHECK_EQUAL(hbox.count(), 2);
hbox.clear();
BOOST_CHECK_EQUAL(hbox.count(), 0);
}
//------------------------------------------------------------------------------
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, 25));
BOOST_CHECK_EQUAL(w2->geometry(), SGRecti(0, 30, 50, 51));
BOOST_CHECK_EQUAL(w_no_hfw->geometry(), SGRecti(0, 86, 50, 36));
// Same geometry as before -> should get same widget geometry
// (check internal size hint cache updates correctly)
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));
}
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE( nasal_widget )
{
naContext c = naNewContext();
naRef me = naNewHash(c);
sc::NasalWidgetRef w( new sc::NasalWidget(me) );
// Default layout sizes (no user set values)
BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(16, 16));
BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(32, 32));
BOOST_CHECK_EQUAL(w->maximumSize(), sc::LayoutItem::MAX_SIZE);
// Changed layout sizes
w->setLayoutMinimumSize( SGVec2i(2, 12) );
w->setLayoutSizeHint( SGVec2i(3, 13) );
w->setLayoutMaximumSize( SGVec2i(4, 14) );
BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 12));
BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(3, 13));
BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 14));
// User set values (overwrite layout sizes)
w->setMinimumSize( SGVec2i(15, 16) );
w->setSizeHint( SGVec2i(17, 18) );
w->setMaximumSize( SGVec2i(19, 20) );
BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(15, 16));
BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(17, 18));
BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(19, 20));
// Only vertical user set values (layout/default for horizontal hints)
w->setMinimumSize( SGVec2i(0, 21) );
w->setSizeHint( SGVec2i(0, 22) );
w->setMaximumSize( SGVec2i(SGLimits<int>::max(), 23) );
BOOST_CHECK_EQUAL(w->minimumSize(), SGVec2i(2, 21));
BOOST_CHECK_EQUAL(w->sizeHint(), SGVec2i(3, 22));
BOOST_CHECK_EQUAL(w->maximumSize(), SGVec2i(4, 23));
naFreeContext(c);
}

Some files were not shown because too many files have changed in this diff Show More