#include <osg/PolygonMode>
#include <osg/Material>
#include <osgGA/GUIEventAdapter>
-
+
+#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 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;
fireBindingList(_bindingsDown);
_repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
return true;
}
- virtual void buttonReleased(void)
+ virtual void buttonReleased(int keyModState)
{
+ SG_UNUSED(keyModState);
fireBindingList(_bindingsUp);
}
- virtual void update(double dt)
+
+ virtual void update(double dt, int keyModState)
{
+ SG_UNUSED(keyModState);
if (!_repeatable)
return;
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 {
bool _done;
};
+///////////////////////////////////////////////////////////////////////////////
class SGPickAnimation::VncCallback : public SGPickCallback {
public:
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;
double _squaredRight, _squaredDown;
};
+///////////////////////////////////////////////////////////////////////////////
+
SGPickAnimation::SGPickAnimation(const SGPropertyNode* configNode,
SGPropertyNode* modelRoot) :
SGAnimation(configNode, modelRoot)
}
}
-class SGKnobAnimation::KnobPickCallback : public SGPickCallback {
+static bool static_knobMouseWheelAlternateDirection = false;
+static bool static_knobDragAlternateAxis = false;
+static double static_dragSensitivity = 1.0;
+
+class KnobSliderPickCallback : public SGPickCallback {
public:
- KnobPickCallback(const SGPropertyNode* configNode,
+ 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)),
- _stickyShifted(false)
+ _dragDirection(DRAG_DEFAULT)
{
- const SGPropertyNode* act = configNode->getChild("action");
- if (act)
- _action = readBindingList(act->getChildren("binding"), modelRoot);
+ readOptionalBindingList(configNode, modelRoot, "action", _action);
+ readOptionalBindingList(configNode, modelRoot, "increase", _bindingsIncrease);
+ readOptionalBindingList(configNode, modelRoot, "decrease", _bindingsDecrease);
- const SGPropertyNode* cw = configNode->getChild("cw");
- if (cw)
- _bindingsCW = readBindingList(cw->getChildren("binding"), modelRoot);
+ readOptionalBindingList(configNode, modelRoot, "release", _releaseAction);
+ readOptionalBindingList(configNode, modelRoot, "hovered", _hover);
- const SGPropertyNode* ccw = configNode->getChild("ccw");
- if (ccw)
- _bindingsCCW = readBindingList(ccw->getChildren("binding"), modelRoot);
-
- if (configNode->hasChild("shift-action") || configNode->hasChild("shift-cw") ||
- configNode->hasChild("shift-ccw"))
+ if (configNode->hasChild("shift-action") || configNode->hasChild("shift-increase") ||
+ configNode->hasChild("shift-decrease"))
{
// explicit shifted behaviour - just do exactly what was provided
- const SGPropertyNode* act = configNode->getChild("shift-action");
- if (act)
- _shiftedAction = readBindingList(act->getChildren("binding"), modelRoot);
-
- const SGPropertyNode* cw = configNode->getChild("shift-cw");
- if (cw)
- _shiftedCW = readBindingList(cw->getChildren("binding"), modelRoot);
-
- const SGPropertyNode* ccw = configNode->getChild("shift-ccw");
- if (ccw)
- _shiftedCCW = readBindingList(ccw->getChildren("binding"), modelRoot);
+ 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(_bindingsCW, _shiftedCW, shiftRepeat);
- repeatBindings(_bindingsCCW, _shiftedCCW, 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&)
- {
- _stickyShifted = ea->getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_SHIFT;
-
+ {
// 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 == 4)) {
- _direction = DIRECTION_CLOCKWISE;
- } else if ((button == 1) || (button == 3)) {
- _direction = DIRECTION_ANTICLOCKWISE;
+ if ((button == 0) || (button == increaseMouseWheel)) {
+ _direction = DIRECTION_INCREASE;
+ } else if ((button == 1) || (button == decreaseMouseWheel)) {
+ _direction = DIRECTION_DECREASE;
} else {
return false;
}
- _repeatTime = -_repeatInterval; // anti-bobble: delay start of repeat
- fire(_stickyShifted);
+ _lastFirePos = eventToWindowCoords(ea);
+ // delay start of repeat, makes dragging more usable
+ _repeatTime = -_repeatInterval;
+ _hasDragged = false;
return true;
}
- virtual void buttonReleased(void)
+ virtual void buttonReleased(int keyModState)
{
+ // for *clicks*, we only fire on button release
+ if (!_hasDragged) {
+ fire(keyModState & osgGA::GUIEventAdapter::MODKEY_SHIFT, _direction);
+ }
+
+ fireBindingList(_releaseAction);
+ }
+
+ 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)
+ virtual void update(double dt, int keyModState)
{
+ if (_hasDragged) {
+ return;
+ }
+
_repeatTime += dt;
while (_repeatInterval < _repeatTime) {
_repeatTime -= _repeatInterval;
- fire(_stickyShifted);
+ 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)
+ void fire(bool isShifted, Direction dir)
{
const SGBindingList& act(isShifted ? _shiftedAction : _action);
- const SGBindingList& cw(isShifted ? _shiftedCW : _bindingsCW);
- const SGBindingList& ccw(isShifted ? _shiftedCCW : _bindingsCCW);
+ const SGBindingList& incr(isShifted ? _shiftedIncrease : _bindingsIncrease);
+ const SGBindingList& decr(isShifted ? _shiftedDecrease : _bindingsDecrease);
- switch (_direction) {
- case DIRECTION_CLOCKWISE:
+ switch (dir) {
+ case DIRECTION_INCREASE:
fireBindingListWithOffset(act, 1, 1);
- fireBindingList(cw);
+ fireBindingList(incr);
break;
- case DIRECTION_ANTICLOCKWISE:
+ case DIRECTION_DECREASE:
fireBindingListWithOffset(act, -1, 1);
- fireBindingList(ccw);
+ fireBindingList(decr);
break;
default: break;
}
}
SGBindingList _action, _shiftedAction;
- SGBindingList _bindingsCW, _shiftedCW,
- _bindingsCCW, _shiftedCCW;
-
- enum Direction
- {
- DIRECTION_NONE,
- DIRECTION_CLOCKWISE,
- DIRECTION_ANTICLOCKWISE
- };
+ SGBindingList _releaseAction;
+ SGBindingList _bindingsIncrease, _shiftedIncrease,
+ _bindingsDecrease, _shiftedDecrease;
+ SGBindingList _hover;
+
Direction _direction;
double _repeatInterval;
double _repeatTime;
- // FIXME - would be better to pass the current modifier state
- // into update(), but for now let's make it sticky
- bool _stickyShifted;
+ 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) :
transform->setAxis(_axis);
SGSceneUserData* ud = SGSceneUserData::getOrCreateSceneUserData(transform);
- ud->setPickCallback(new KnobPickCallback(getConfig(), getModelRoot()));
+ 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;
+}