]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/model/SGPickAnimation.cxx
slider - fix shifted check while dragging
[simgear.git] / simgear / scene / model / SGPickAnimation.cxx
index a282df192385f06cee018c97834141d8e21669a6..abdeec8229a4e515fc6c67784527ef73bf8e9de8 100644 (file)
 #include <osg/PolygonOffset>
 #include <osg/PolygonMode>
 #include <osg/Material>
+#include <osgGA/GUIEventAdapter>
 
-#include <simgear/scene/material/EffectGeode.hxx>
+#include <simgear/sg_inlines.h>
 #include <simgear/scene/util/SGPickCallback.hxx>
+#include <simgear/scene/material/EffectGeode.hxx>
 #include <simgear/scene/util/SGSceneUserData.hxx>
 #include <simgear/structure/SGBinding.hxx>
 #include <simgear/scene/util/StateAttributeFactory.hxx>
+#include <simgear/scene/model/SGRotateTransform.hxx>
+#include <simgear/scene/model/SGTranslateTransform.hxx>
 
 using namespace simgear;
 
 using OpenThreads::Mutex;
 using OpenThreads::ScopedLock;
 
+static void readOptionalBindingList(const SGPropertyNode* aNode, SGPropertyNode* modelRoot,
+    const std::string& aName, SGBindingList& aBindings)
+{
+    const SGPropertyNode* n = aNode->getChild(aName);
+    if (n)
+        aBindings = readBindingList(n->getChildren("binding"), modelRoot);
+    
+}
+
+
+osg::Vec2d eventToWindowCoords(const osgGA::GUIEventAdapter* ea)
+{
+    using namespace osg;
+    const GraphicsContext* gc = ea->getGraphicsContext();
+    const GraphicsContext::Traits* traits = gc->getTraits() ;
+    // Scale x, y to the dimensions of the window
+    double x = (((ea->getX() - ea->getXmin()) / (ea->getXmax() - ea->getXmin()))
+         * (double)traits->width);
+    double y = (((ea->getY() - ea->getYmin()) / (ea->getYmax() - ea->getYmin()))
+         * (double)traits->height);
+    if (ea->getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS)
+        y = (double)traits->height - y;
+    
+    return osg::Vec2d(x, y);
+}
+
  class SGPickAnimation::PickCallback : public SGPickCallback {
  public:
    PickCallback(const SGPropertyNode* configNode,
                 SGPropertyNode* modelRoot) :
+     SGPickCallback(PriorityPanel),
      _repeatable(configNode->getBoolValue("repeatable", false)),
      _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1))
    {
-     SG_LOG(SG_INPUT, SG_DEBUG, "Reading all bindings");
      std::vector<SGPropertyNode_ptr> bindings;
 
      bindings = configNode->getChildren("button");
      for (unsigned int i = 0; i < bindings.size(); ++i) {
-       _buttons.push_back( bindings[i]->getIntValue() );
-     }
-     bindings = configNode->getChildren("binding");
-     for (unsigned int i = 0; i < bindings.size(); ++i) {
-       _bindingsDown.push_back(new SGBinding(bindings[i], modelRoot));
+       _buttons.insert( bindings[i]->getIntValue() );
      }
 
-     const SGPropertyNode* upNode = configNode->getChild("mod-up");
-     if (!upNode)
-       return;
-     bindings = upNode->getChildren("binding");
-     for (unsigned int i = 0; i < bindings.size(); ++i) {
-       _bindingsUp.push_back(new SGBinding(bindings[i], modelRoot));
+     _bindingsDown = readBindingList(configNode->getChildren("binding"), modelRoot);
+     readOptionalBindingList(configNode, modelRoot, "mod-up", _bindingsUp);
+     readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
+     
+     if (configNode->hasChild("cursor")) {
+       _cursorName = configNode->getStringValue("cursor");
      }
    }
-   virtual bool buttonPressed(int button, const Info&)
+   
+   virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
    {
-     bool found = false;
-     for( std::vector<int>::iterator it = _buttons.begin(); it != _buttons.end(); ++it ) {
-       if( *it == button ) {
-         found = true;
-         break;
+       if (_buttons.find(button) == _buttons.end()) {
+           return false;
        }
-     }
-     if (!found )
-       return false;
-     SGBindingList::const_iterator i;
-     for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
-       (*i)->fire();
+       
+     fireBindingList(_bindingsDown);
      _repeatTime = -_repeatInterval;    // anti-bobble: delay start of repeat
      return true;
    }
-   virtual void buttonReleased(void)
+   virtual void buttonReleased(int keyModState)
    {
-     SGBindingList::const_iterator i;
-     for (i = _bindingsUp.begin(); i != _bindingsUp.end(); ++i)
-       (*i)->fire();
+       SG_UNUSED(keyModState);
+       fireBindingList(_bindingsUp);
    }
-   virtual void update(double dt)
+     
+   virtual void update(double dt, int keyModState)
    {
+     SG_UNUSED(keyModState);
      if (!_repeatable)
        return;
 
      _repeatTime += dt;
      while (_repeatInterval < _repeatTime) {
        _repeatTime -= _repeatInterval;
-       SGBindingList::const_iterator i;
-       for (i = _bindingsDown.begin(); i != _bindingsDown.end(); ++i)
-         (*i)->fire();
+         fireBindingList(_bindingsDown);
      }
    }
+   
+   virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
+   {
+       if (_hover.empty()) {
+           return false;
+       }
+       
+       SGPropertyNode_ptr params(new SGPropertyNode);
+       params->setDoubleValue("x", windowPos.x());
+       params->setDoubleValue("y", windowPos.y());
+       fireBindingList(_hover, params.ptr());
+       return true;
+   }
+   
+   std::string getCursor() const
+   { return _cursorName; }
  private:
    SGBindingList _bindingsDown;
    SGBindingList _bindingsUp;
-   std::vector<int> _buttons;
+   SGBindingList _hover;
+   std::set<int> _buttons;
    bool _repeatable;
    double _repeatInterval;
    double _repeatTime;
+   std::string _cursorName;
  };
 
  class VncVisitor : public osg::NodeVisitor {
@@ -186,6 +223,7 @@ using OpenThreads::ScopedLock;
    bool _done;
  };
 
+///////////////////////////////////////////////////////////////////////////////
 
  class SGPickAnimation::VncCallback : public SGPickCallback {
  public:
@@ -210,7 +248,7 @@ using OpenThreads::ScopedLock;
      _squaredDown = dot(_toDown, _toDown);
    }
 
-   virtual bool buttonPressed(int button, const Info& info)
+   virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info& info)
    {
      SGVec3d loc(info.local);
      SG_LOG(SG_INPUT, SG_DEBUG, "VNC pressed " << button << ": " << loc);
@@ -224,15 +262,14 @@ using OpenThreads::ScopedLock;
      return vv.wasSuccessful();
 
    }
-   virtual void buttonReleased(void)
+   virtual void buttonReleased(int keyModState)
    {
+     SG_UNUSED(keyModState);
      SG_LOG(SG_INPUT, SG_DEBUG, "VNC release");
      VncVisitor vv(_x, _y, 0);
      _node->accept(vv);
    }
-   virtual void update(double dt)
-   {
-   }
+
  private:
    double _x, _y;
    osg::ref_ptr<osg::Group> _node;
@@ -240,6 +277,8 @@ using OpenThreads::ScopedLock;
    double _squaredRight, _squaredDown;
  };
 
+///////////////////////////////////////////////////////////////////////////////
+
  SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
                                   SGPropertyNode* modelRoot) :
    SGAnimation(configNode, modelRoot)
@@ -252,23 +291,81 @@ using OpenThreads::ScopedLock;
  osg::ref_ptr<osg::Uniform> colorModeUniform;
  }
 
+
+void 
+SGPickAnimation::innerSetupPickGroup(osg::Group* commonGroup, osg::Group& parent)
+{
+    // Contains the normal geometry that is interactive
+    osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
+    normalGroup->setName("pick normal group");
+    normalGroup->addChild(commonGroup);
+    
+    // Used to render the geometry with just yellow edges
+    osg::Group* highlightGroup = new osg::Group;
+    highlightGroup->setName("pick highlight group");
+    highlightGroup->setNodeMask(simgear::PICK_BIT);
+    highlightGroup->addChild(commonGroup);
+    
+    // prepare a state set that paints the edges of this object yellow
+    // The material and texture attributes are set with
+    // OVERRIDE|PROTECTED in case there is a material animation on a
+    // higher node in the scene graph, which would have its material
+    // attribute set with OVERRIDE.
+    osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
+    osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
+    stateSet->setTextureAttributeAndModes(0, white,
+                                          (osg::StateAttribute::ON
+                                           | osg::StateAttribute::OVERRIDE
+                                           | osg::StateAttribute::PROTECTED));
+    osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
+    polygonOffset->setFactor(-1);
+    polygonOffset->setUnits(-1);
+    stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
+    stateSet->setMode(GL_POLYGON_OFFSET_LINE,
+                      osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
+    osg::PolygonMode* polygonMode = new osg::PolygonMode;
+    polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
+                         osg::PolygonMode::LINE);
+    stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
+    osg::Material* material = new osg::Material;
+    material->setColorMode(osg::Material::OFF);
+    material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
+    // XXX Alpha < 1.0 in the diffuse material value is a signal to the
+    // default shader to take the alpha value from the material value
+    // and not the glColor. In many cases the pick animation geometry is
+    // transparent, so the outline would not be visible without this hack.
+    material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
+    material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
+    material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
+    stateSet->setAttribute(
+                           material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
+    // The default shader has a colorMode uniform that mimics the
+    // behavior of Material color mode.
+    osg::Uniform* cmUniform = 0;
+    {
+        ScopedLock<Mutex> lock(colorModeUniformMutex);
+        if (!colorModeUniform.valid()) {
+            colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
+            colorModeUniform->set(0); // MODE_OFF
+            colorModeUniform->setDataVariance(osg::Object::STATIC);
+        }
+        cmUniform = colorModeUniform.get();
+    }
+    stateSet->addUniform(cmUniform,
+                         osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
+    
+    // Only add normal geometry if configured
+    if (getConfig()->getBoolValue("visible", true))
+        parent.addChild(normalGroup.get());
+    parent.addChild(highlightGroup);
+}
+
  osg::Group*
  SGPickAnimation::createAnimationGroup(osg::Group& parent)
  {
-   osg::Group* commonGroup = new osg::Group;
-
-   // Contains the normal geometry that is interactive
-   osg::ref_ptr<osg::Group> normalGroup = new osg::Group;
-   normalGroup->setName("pick normal group");
-   normalGroup->addChild(commonGroup);
-
-   // Used to render the geometry with just yellow edges
-   osg::Group* highlightGroup = new osg::Group;
-   highlightGroup->setName("pick highlight group");
-   highlightGroup->setNodeMask(simgear::PICK_BIT);
-   highlightGroup->addChild(commonGroup);
-   SGSceneUserData* ud;
-   ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
+     osg::Group* commonGroup = new osg::Group;
+     innerSetupPickGroup(commonGroup, parent);
+     SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(commonGroup);
 
    // add actions that become macro and command invocations
    std::vector<SGPropertyNode_ptr> actions;
@@ -281,59 +378,356 @@ using OpenThreads::ScopedLock;
      ud->addPickCallback(new VncCallback(actions[i], getModelRoot(),
        &parent));
 
-   // prepare a state set that paints the edges of this object yellow
-   // The material and texture attributes are set with
-   // OVERRIDE|PROTECTED in case there is a material animation on a
-   // higher node in the scene graph, which would have its material
-   // attribute set with OVERRIDE.
-   osg::StateSet* stateSet = highlightGroup->getOrCreateStateSet();
-   osg::Texture2D* white = StateAttributeFactory::instance()->getWhiteTexture();
-   stateSet->setTextureAttributeAndModes(0, white,
-                                         (osg::StateAttribute::ON
-                                          | osg::StateAttribute::OVERRIDE
-                                          | osg::StateAttribute::PROTECTED));
-   osg::PolygonOffset* polygonOffset = new osg::PolygonOffset;
-   polygonOffset->setFactor(-1);
-   polygonOffset->setUnits(-1);
-   stateSet->setAttribute(polygonOffset, osg::StateAttribute::OVERRIDE);
-   stateSet->setMode(GL_POLYGON_OFFSET_LINE,
-                     osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
-   osg::PolygonMode* polygonMode = new osg::PolygonMode;
-   polygonMode->setMode(osg::PolygonMode::FRONT_AND_BACK,
-                        osg::PolygonMode::LINE);
-   stateSet->setAttribute(polygonMode, osg::StateAttribute::OVERRIDE);
-   osg::Material* material = new osg::Material;
-   material->setColorMode(osg::Material::OFF);
-   material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 1));
-   // XXX Alpha < 1.0 in the diffuse material value is a signal to the
-   // default shader to take the alpha value from the material value
-   // and not the glColor. In many cases the pick animation geometry is
-   // transparent, so the outline would not be visible without this hack.
-   material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, .95));
-   material->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1, 1, 0, 1));
-   material->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0, 0, 0, 0));
-   stateSet->setAttribute(
-       material, osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED);
-   // The default shader has a colorMode uniform that mimics the
-   // behavior of Material color mode.
-   osg::Uniform* cmUniform = 0;
-   {
-       ScopedLock<Mutex> lock(colorModeUniformMutex);
-       if (!colorModeUniform.valid()) {
-           colorModeUniform = new osg::Uniform(osg::Uniform::INT, "colorMode");
-           colorModeUniform->set(0); // MODE_OFF
-           colorModeUniform->setDataVariance(osg::Object::STATIC);
-       }
-       cmUniform = colorModeUniform.get();
-   }
-   stateSet->addUniform(cmUniform,
-                        osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON);
-   // Only add normal geometry if configured
-   if (getConfig()->getBoolValue("visible", true))
-     parent.addChild(normalGroup.get());
-   parent.addChild(highlightGroup);
+   return commonGroup;
+}
+
+///////////////////////////////////////////////////////////////////////////
 
+// insert count copies of binding list A, into the output list.
+// effectively makes the output list fire binding A multiple times
+// in sequence
+static void repeatBindings(const SGBindingList& a, SGBindingList& out, int count)
+{
+    out.clear();
+    for (int i=0; i<count; ++i) {
+        out.insert(out.end(), a.begin(), a.end());
+    }
+}
+
+static bool static_knobMouseWheelAlternateDirection = false;
+static bool static_knobDragAlternateAxis = false;
+static double static_dragSensitivity = 1.0;
+
+class KnobSliderPickCallback : public SGPickCallback {
+public:
+    enum Direction
+    {
+        DIRECTION_NONE,
+        DIRECTION_INCREASE,
+        DIRECTION_DECREASE
+    };
+    
+    enum DragDirection
+    {
+        DRAG_DEFAULT = 0,
+        DRAG_VERTICAL,
+        DRAG_HORIZONTAL
+    };
+
+    
+    KnobSliderPickCallback(const SGPropertyNode* configNode,
+                 SGPropertyNode* modelRoot) :
+        SGPickCallback(PriorityPanel),
+        _direction(DIRECTION_NONE),
+        _repeatInterval(configNode->getDoubleValue("interval-sec", 0.1)),
+        _dragDirection(DRAG_DEFAULT)
+    {
+        readOptionalBindingList(configNode, modelRoot, "action", _action);
+        readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
+        readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
+        
+        readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
+        readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
+        
+        if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
+            configNode->hasChild("shift-decrease"))
+        {
+        // explicit shifted behaviour - just do exactly what was provided
+            readOptionalBindingList(configNode, modelRoot, "shift-action", _shiftedAction);
+            readOptionalBindingList(configNode, modelRoot, "shift-increase", _shiftedIncrease);
+            readOptionalBindingList(configNode, modelRoot, "shift-decrease", _shiftedDecrease);
+        } else {
+            // default shifted behaviour - repeat normal bindings N times.
+            int shiftRepeat = configNode->getIntValue("shift-repeat", 10);
+            repeatBindings(_action, _shiftedAction, shiftRepeat);
+            repeatBindings(_bindingsIncrease, _shiftedIncrease, shiftRepeat);
+            repeatBindings(_bindingsDecrease, _shiftedDecrease, shiftRepeat);
+        } // of default shifted behaviour
+        
+        _dragScale = configNode->getDoubleValue("drag-scale-px", 10.0);
+        std::string dragDir = configNode->getStringValue("drag-direction");
+        if (dragDir == "vertical") {
+            _dragDirection = DRAG_VERTICAL;
+        } else if (dragDir == "horizontal") {
+            _dragDirection = DRAG_HORIZONTAL;
+        }
+      
+        if (configNode->hasChild("cursor")) {
+            _cursorName = configNode->getStringValue("cursor");
+        } else {
+          DragDirection dir = effectiveDragDirection();
+          if (dir == DRAG_VERTICAL) {
+            _cursorName = "drag-vertical";
+          } else if (dir == DRAG_HORIZONTAL) {
+            _cursorName = "drag-horizontal";
+          }
+        }
+    }
+    
+    virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter* ea, const Info&)
+    {        
+        // the 'be nice to Mac / laptop' users option; alt-clicking spins the
+        // opposite direction. Should make this configurable
+        if ((button == 0) && (ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) {
+            button = 1;
+        }
+        
+        int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4;
+        int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3;
+            
+        _direction = DIRECTION_NONE;
+        if ((button == 0) || (button == increaseMouseWheel)) {
+            _direction = DIRECTION_INCREASE;
+        } else if ((button == 1) || (button == decreaseMouseWheel)) {
+            _direction = DIRECTION_DECREASE;
+        } else {
+            return false;
+        }
+        
+        _lastFirePos = eventToWindowCoords(ea);
+    // delay start of repeat, makes dragging more usable
+        _repeatTime = -_repeatInterval;    
+        _hasDragged = false;
+        return true;
+    }
+    
+    virtual void buttonReleased(int keyModState)
+    {
+        // for *clicks*, we only fire on button release
+        if (!_hasDragged) {
+            fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
+        }
+        
+        fireBindingList(_releaseAction);
+    }
   
-   return commonGroup;
+    DragDirection effectiveDragDirection() const
+    {
+      if (_dragDirection == DRAG_DEFAULT) {
+        // respect the current default settings - this allows runtime
+        // setting of the default drag direction.
+        return static_knobDragAlternateAxis ? DRAG_VERTICAL : DRAG_HORIZONTAL;
+      }
+      
+      return _dragDirection;
+  }
+  
+    virtual void mouseMoved(const osgGA::GUIEventAdapter* ea)
+    {
+        _mousePos = eventToWindowCoords(ea);
+        osg::Vec2d deltaMouse = _mousePos - _lastFirePos;
+        
+        if (!_hasDragged) {
+            
+            double manhattanDist = deltaMouse.x() * deltaMouse.x()  + deltaMouse.y() * deltaMouse.y();
+            if (manhattanDist < 5) {
+                // don't do anything, just input noise
+                return;
+            }
+            
+        // user is dragging, disable repeat behaviour
+            _hasDragged = true;
+        }
+      
+        double delta = (effectiveDragDirection() == DRAG_VERTICAL) ? deltaMouse.y() : deltaMouse.x();
+    // per-animation scale factor lets the aircraft author tune for expectations,
+    // eg heading setting vs 5-state switch.
+    // then we scale by a global sensitivity, which the user can set.
+        delta *= static_dragSensitivity / _dragScale;
+        
+        if (fabs(delta) >= 1.0) {
+            // determine direction from sign of delta
+            Direction dir = (delta > 0.0) ? DIRECTION_INCREASE : DIRECTION_DECREASE;
+            fire(ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT, dir);
+            _lastFirePos = _mousePos;
+        }
+    }
+    
+    virtual void update(double dt, int keyModState)
+    {
+        if (_hasDragged) {
+            return;
+        }
+        
+        _repeatTime += dt;
+        while (_repeatInterval < _repeatTime) {
+            _repeatTime -= _repeatInterval;
+            fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
+        } // of repeat iteration
+    }
+
+    virtual bool hover(const osg::Vec2d& windowPos, const Info& info)
+    {
+        if (_hover.empty()) {
+            return false;
+        }
+       
+        SGPropertyNode_ptr params(new SGPropertyNode);
+        params->setDoubleValue("x", windowPos.x());
+        params->setDoubleValue("y", windowPos.y());
+        fireBindingList(_hover, params.ptr());
+        return true;
+    }
+  
+    void setCursor(const std::string& aName)
+    {
+      _cursorName = aName;
+    }
+  
+    virtual std::string getCursor() const
+    { return _cursorName; }
+  
+private:
+    void fire(bool isShifted, Direction dir)
+    {
+        const SGBindingList& act(isShifted ? _shiftedAction : _action);
+        const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
+        const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
+        
+        switch (dir) {
+            case DIRECTION_INCREASE:
+                fireBindingListWithOffset(act,  1, 1);
+                fireBindingList(incr);
+                break;
+            case DIRECTION_DECREASE:
+                fireBindingListWithOffset(act, -1, 1);
+                fireBindingList(decr);
+                break;
+            default: break;
+        }
+    }
+    
+    SGBindingList _action, _shiftedAction;
+    SGBindingList _releaseAction;
+    SGBindingList _bindingsIncrease, _shiftedIncrease,
+        _bindingsDecrease, _shiftedDecrease;
+    SGBindingList _hover;
+    
+        
+    Direction _direction;
+    double _repeatInterval;
+    double _repeatTime;
+    
+    DragDirection _dragDirection;
+    bool _hasDragged; ///< has the mouse been dragged since the press?
+    osg::Vec2d _mousePos, ///< current window coords location of the mouse
+        _lastFirePos; ///< mouse location where we last fired the bindings
+    double _dragScale;
+  
+    std::string _cursorName;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SGKnobAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+    UpdateCallback(SGExpressiond const* animationValue) :
+        _animationValue(animationValue)
+    {
+        setName("SGKnobAnimation::UpdateCallback");
+    }
+    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+    {
+        SGRotateTransform* transform = static_cast<SGRotateTransform*>(node);
+        transform->setAngleDeg(_animationValue->getValue());
+        traverse(node, nv);
+    }
+    
+private:
+    SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+
+SGKnobAnimation::SGKnobAnimation(const SGPropertyNode* configNode,
+                                 SGPropertyNode* modelRoot) :
+    SGPickAnimation(configNode, modelRoot)
+{
+    SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-deg",
+                                                  -SGLimitsd::max(), SGLimitsd::max());
+    _animationValue = value->simplify();
+    
+    
+    readRotationCenterAndAxis(configNode, _center, _axis);
+}
+
+
+osg::Group*
+SGKnobAnimation::createAnimationGroup(osg::Group& parent)
+{
+    SGRotateTransform* transform = new SGRotateTransform();
+    innerSetupPickGroup(transform, parent);
+    
+    UpdateCallback* uc = new UpdateCallback(_animationValue);
+    transform->setUpdateCallback(uc);
+    transform->setCenter(_center);
+    transform->setAxis(_axis);
+        
+    SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
+    ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
+    
+    return transform;
+}
+
+void SGKnobAnimation::setAlternateMouseWheelDirection(bool aToggle)
+{
+    static_knobMouseWheelAlternateDirection = aToggle;
+}
+
+void SGKnobAnimation::setAlternateDragAxis(bool aToggle)
+{
+    static_knobDragAlternateAxis = aToggle;
+}
+
+void SGKnobAnimation::setDragSensitivity(double aFactor)
+{
+    static_dragSensitivity = aFactor;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SGSliderAnimation::UpdateCallback : public osg::NodeCallback {
+public:
+    UpdateCallback(SGExpressiond const* animationValue) :
+    _animationValue(animationValue)
+    {
+        setName("SGSliderAnimation::UpdateCallback");
+    }
+    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+    {
+        SGTranslateTransform* transform = static_cast<SGTranslateTransform*>(node);
+        transform->setValue(_animationValue->getValue());
+
+        traverse(node, nv);
+    }
+    
+private:
+    SGSharedPtr<SGExpressiond const> _animationValue;
+};
+
+
+SGSliderAnimation::SGSliderAnimation(const SGPropertyNode* configNode,
+                                 SGPropertyNode* modelRoot) :
+    SGPickAnimation(configNode, modelRoot)
+{
+    SGSharedPtr<SGExpressiond> value = read_value(configNode, modelRoot, "-m",
+                                                  -SGLimitsd::max(), SGLimitsd::max());
+    _animationValue = value->simplify();
+    
+    _axis = readTranslateAxis(configNode);
+}
+
+osg::Group*
+SGSliderAnimation::createAnimationGroup(osg::Group& parent)
+{
+    SGTranslateTransform* transform = new SGTranslateTransform();
+    innerSetupPickGroup(transform, parent);
+    
+    UpdateCallback* uc = new UpdateCallback(_animationValue);
+    transform->setUpdateCallback(uc);
+    transform->setAxis(_axis);
+    
+    SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
+    ud->setPickCallback(new KnobSliderPickCallback(getConfig(), getModelRoot()));
+    
+    return transform;
 }
-  
\ No newline at end of file