| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <Inventor/SoFullPath.h> |
| | #include <Inventor/SoPickedPoint.h> |
| | #include <Inventor/actions/SoRayPickAction.h> |
| | #include <Inventor/draggers/SoDragger.h> |
| | #include <QApplication> |
| |
|
| |
|
| | #include <QTapAndHoldGesture> |
| |
|
| | #include <FCConfig.h> |
| |
|
| | #include <App/Application.h> |
| | #include <Base/Interpreter.h> |
| | #include <Base/Console.h> |
| |
|
| | #include "GestureNavigationStyle.h" |
| | #include "Application.h" |
| | #include "SoTouchEvents.h" |
| | #include "View3DInventorViewer.h" |
| |
|
| | #include <boost/statechart/custom_reaction.hpp> |
| | #include <boost/statechart/state_machine.hpp> |
| | #include <boost/statechart/state.hpp> |
| |
|
| |
|
| | namespace sc = boost::statechart; |
| | #define NS Gui::GestureNavigationStyle |
| |
|
| | namespace Gui |
| | { |
| |
|
| | class NS::Event: public sc::event<NS::Event> |
| | { |
| | public: |
| | Event() |
| | : flags(new Flags) |
| | {} |
| | virtual ~Event() = default; |
| |
|
| | void log() const |
| | { |
| | if (isPress(1)) { |
| | Base::Console().log("button1 press "); |
| | } |
| | if (isPress(2)) { |
| | Base::Console().log("button2 press "); |
| | } |
| | if (isPress(3)) { |
| | Base::Console().log("button3 press "); |
| | } |
| | if (isRelease(1)) { |
| | Base::Console().log("button1 release "); |
| | } |
| | if (isRelease(2)) { |
| | Base::Console().log("button2 release "); |
| | } |
| | if (isRelease(3)) { |
| | Base::Console().log("button3 release "); |
| | } |
| | if (isMouseButtonEvent()) { |
| | Base::Console().log("%x", modifiers); |
| | } |
| | if (isGestureEvent()) { |
| | Base::Console().log("Gesture "); |
| | switch (asGestureEvent()->state) { |
| | case SoGestureEvent::SbGSStart: |
| | Base::Console().log("start "); |
| | break; |
| | case SoGestureEvent::SbGSEnd: |
| | Base::Console().log("end "); |
| | break; |
| | case SoGestureEvent::SbGSUpdate: |
| | Base::Console().log("data "); |
| | break; |
| | default: |
| | Base::Console().log("??? "); |
| | } |
| |
|
| | Base::Console().log(inventor_event->getTypeId().getName().getString()); |
| | } |
| | if (isMouseButtonEvent() || isGestureEvent()) { |
| | Base::Console().log( |
| | "(%i,%i)\n", |
| | inventor_event->getPosition()[0], |
| | inventor_event->getPosition()[1] |
| | ); |
| | } |
| | } |
| |
|
| | |
| | bool isMouseButtonEvent() const |
| | { |
| | return this->inventor_event->isOfType(SoMouseButtonEvent::getClassTypeId()); |
| | } |
| | const SoMouseButtonEvent* asMouseButtonEvent() const |
| | { |
| | return static_cast<const SoMouseButtonEvent*>(this->inventor_event); |
| | } |
| | bool isPress(int button_index) const |
| | { |
| | if (!isMouseButtonEvent()) { |
| | return false; |
| | } |
| | int sobtn = SoMouseButtonEvent::BUTTON1 + button_index - 1; |
| | return asMouseButtonEvent()->getButton() == sobtn |
| | && asMouseButtonEvent()->getState() == SoMouseButtonEvent::DOWN; |
| | } |
| | bool isRelease(int button_index) const |
| | { |
| | if (!isMouseButtonEvent()) { |
| | return false; |
| | } |
| | int sobtn = SoMouseButtonEvent::BUTTON1 + button_index - 1; |
| | return asMouseButtonEvent()->getButton() == sobtn |
| | && asMouseButtonEvent()->getState() == SoMouseButtonEvent::UP; |
| | } |
| | bool isKeyboardEvent() const |
| | { |
| | return this->inventor_event->isOfType(SoKeyboardEvent::getClassTypeId()); |
| | } |
| | const SoKeyboardEvent* asKeyboardEvent() const |
| | { |
| | return static_cast<const SoKeyboardEvent*>(this->inventor_event); |
| | } |
| | bool isLocation2Event() const |
| | { |
| | return this->inventor_event->isOfType(SoLocation2Event::getClassTypeId()); |
| | } |
| | const SoLocation2Event* asLocation2Event() const |
| | { |
| | return static_cast<const SoLocation2Event*>(this->inventor_event); |
| | } |
| | bool isMotion3Event() const |
| | { |
| | return this->inventor_event->isOfType(SoMotion3Event::getClassTypeId()); |
| | } |
| | bool isGestureEvent() const |
| | { |
| | return this->inventor_event->isOfType(SoGestureEvent::getClassTypeId()); |
| | } |
| | const SoGestureEvent* asGestureEvent() const |
| | { |
| | return static_cast<const SoGestureEvent*>(this->inventor_event); |
| | } |
| | bool isGestureActive() const |
| | { |
| | if (!isGestureEvent()) { |
| | return false; |
| | } |
| | if (asGestureEvent()->state == SoGestureEvent::SbGSStart |
| | || asGestureEvent()->state == SoGestureEvent::SbGSUpdate) { |
| | return true; |
| | } |
| | else { |
| | return false; |
| | } |
| | } |
| |
|
| | public: |
| | enum |
| | { |
| | |
| | BUTTON1DOWN = 0x00000100, |
| | BUTTON2DOWN = 0x00000001, |
| | BUTTON3DOWN = 0x00000010, |
| | CTRLDOWN = 0x00100000, |
| | SHIFTDOWN = 0x01000000, |
| | ALTDOWN = 0x00010000, |
| | MASKBUTTONS = BUTTON1DOWN | BUTTON2DOWN | BUTTON3DOWN, |
| | MASKMODIFIERS = CTRLDOWN | SHIFTDOWN | ALTDOWN |
| | }; |
| |
|
| | public: |
| | const SoEvent* inventor_event {nullptr}; |
| | unsigned int modifiers {0}; |
| | unsigned int mbstate() const |
| | { |
| | return modifiers & MASKBUTTONS; |
| | } |
| | unsigned int kbdstate() const |
| | { |
| | return modifiers & MASKMODIFIERS; |
| | } |
| |
|
| | struct Flags |
| | { |
| | bool processed = false; |
| | bool propagated = false; |
| | }; |
| | std::shared_ptr<Flags> flags; |
| | |
| | |
| | |
| | |
| | |
| | }; |
| |
|
| | |
| |
|
| | class NS::NaviMachine: public sc::state_machine<NS::NaviMachine, NS::IdleState> |
| | { |
| | public: |
| | using superclass = sc::state_machine<NS::NaviMachine, NS::IdleState>; |
| |
|
| | explicit NaviMachine(NS& ns) |
| | : ns(ns) |
| | {} |
| | NS& ns; |
| |
|
| | public: |
| | virtual void processEvent(NS::Event& ev) |
| | { |
| | if (ns.logging) { |
| | ev.log(); |
| | } |
| | this->process_event(ev); |
| | } |
| | }; |
| |
|
| | class NS::IdleState: public sc::state<NS::IdleState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | explicit IdleState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setViewingMode(NavigationStyle::IDLE); |
| | if (ns.logging) { |
| | Base::Console().log(" -> IdleState\n"); |
| | } |
| | } |
| | virtual ~IdleState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| |
|
| | auto posn = ns.normalizePixelPos(ev.inventor_event->getPosition()); |
| |
|
| | |
| | switch (ns.getViewingMode()) { |
| | case NavigationStyle::SEEK_WAIT_MODE: { |
| | if (ev.isPress(1)) { |
| | ns.seekToPoint(ev.inventor_event->getPosition()); |
| | |
| | ns.setViewingMode(NavigationStyle::SEEK_MODE); |
| | ev.flags->processed = true; |
| | return transit<NS::AwaitingReleaseState>(); |
| | } |
| | }; |
| | |
| | case NavigationStyle::SPINNING: |
| | case NavigationStyle::SEEK_MODE: { |
| | |
| | if (!ev.flags->processed) { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | return transit<NS::AwaitingReleaseState>(); |
| | } |
| | else if (ev.isGestureEvent() || ev.isKeyboardEvent() || ev.isMotion3Event()) { |
| | ns.setViewingMode(NavigationStyle::IDLE); |
| | } |
| | } |
| | } break; |
| | case BOXZOOM: |
| | return forward_event(); |
| | } |
| |
|
| | |
| | if (ev.isPress(1) && ev.mbstate() == 0x100) { |
| | if (ns.isDraggerUnderCursor(ev.inventor_event->getPosition())) { |
| | return transit<NS::InteractState>(); |
| | } |
| | } |
| |
|
| | |
| | if ((ev.isPress(1) && ev.mbstate() == 0x100) || (ev.isPress(2) && ev.mbstate() == 0x001)) { |
| | ns.postponedEvents.post(ev); |
| | ev.flags->processed = true; |
| | return transit<NS::AwaitingMoveState>(); |
| | } |
| |
|
| | |
| | if (ev.isPress(3) && ev.mbstate() == 0x010) { |
| | ev.flags->processed = true; |
| | ns.setupPanningPlane(ns.viewer->getCamera()); |
| | ns.lookAtPoint(ev.inventor_event->getPosition()); |
| | return transit<NS::AwaitingReleaseState>(); |
| | } |
| |
|
| | |
| | if (ev.isGestureActive()) { |
| | ev.flags->processed = true; |
| | return transit<NS::GestureState>(); |
| | } |
| |
|
| | |
| | if (ev.isKeyboardEvent()) { |
| | auto const& kbev = ev.asKeyboardEvent(); |
| | ev.flags->processed = true; |
| | bool press = (kbev->getState() == SoKeyboardEvent::DOWN); |
| | switch (kbev->getKey()) { |
| | case SoKeyboardEvent::H: |
| | |
| | if (!ns.viewer->isEditing() && !press) { |
| | ns.setupPanningPlane(ns.viewer->getCamera()); |
| | ns.lookAtPoint(kbev->getPosition()); |
| | } |
| | break; |
| | case SoKeyboardEvent::PAGE_UP: |
| | if (!press) { |
| | ns.doZoom(ns.viewer->getSoRenderManager()->getCamera(), ns.getDelta(), posn); |
| | } |
| | break; |
| | case SoKeyboardEvent::PAGE_DOWN: |
| | if (!press) { |
| | ns.doZoom(ns.viewer->getSoRenderManager()->getCamera(), -ns.getDelta(), posn); |
| | } |
| | break; |
| | default: |
| | ev.flags->processed = false; |
| | } |
| | } |
| |
|
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::AwaitingMoveState: public sc::state<NS::AwaitingMoveState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| | SbTime since; |
| | int hold_timeout; |
| |
|
| | public: |
| | explicit AwaitingMoveState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | if (ns.logging) { |
| | Base::Console().log(" -> AwaitingMoveState\n"); |
| | } |
| | ns.setViewingMode(NavigationStyle::IDLE); |
| | this->base_pos |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition(); |
| | this->since |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getTime(); |
| |
|
| | ns.mouseMoveThreshold = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetInt("GestureMoveThreshold", ns.mouseMoveThreshold); |
| |
|
| | this->hold_timeout = int(double(QTapAndHoldGesture::timeout()) * 0.9); |
| | this->hold_timeout = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetInt("GestureTapHoldTimeout", this->hold_timeout); |
| | if (this->hold_timeout == 0) { |
| | this->hold_timeout = 650; |
| | } |
| | QTapAndHoldGesture::setTimeout(int(double(this->hold_timeout) / 0.9)); |
| | |
| | |
| | |
| | |
| | } |
| | virtual ~AwaitingMoveState() |
| | { |
| | |
| | this->outermost_context().ns.postponedEvents.discardAll(); |
| | } |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| |
|
| | |
| | auto refire = [&] { |
| | ns.postponedEvents.forwardAll(); |
| | ev.flags->processed = ns.processSoEvent_bypass(ev.inventor_event); |
| | ev.flags->propagated = true; |
| | }; |
| |
|
| | bool long_click = (ev.inventor_event->getTime() - this->since).getValue() * 1000.0 |
| | >= this->hold_timeout; |
| |
|
| | |
| | ev.flags->processed = ev.isMouseButtonEvent() || ev.isLocation2Event(); |
| |
|
| | |
| | if (ev.isRelease(2) && ev.mbstate() == 0 && !ns.viewer->isEditing() |
| | && ns.isPopupMenuEnabled()) { |
| | ns.openPopupMenu(ev.inventor_event->getPosition()); |
| | return transit<NS::IdleState>(); |
| | } |
| |
|
| | |
| | |
| | if (ev.mbstate() == 0x101) { |
| | if (ev.isPress(1)) { |
| | ns.rollDir = -1; |
| | } |
| | if (ev.isPress(2)) { |
| | ns.rollDir = +1; |
| | } |
| | } |
| | |
| | if ((ev.isRelease(1) && ev.mbstate() == 0x001) || (ev.isRelease(2) && ev.mbstate() == 0x100)) { |
| | ns.onRollGesture(ns.rollDir); |
| | return transit<NS::AwaitingReleaseState>(); |
| | } |
| |
|
| | if (ev.isMouseButtonEvent() && ev.mbstate() == 0) { |
| | |
| | if (long_click) { |
| | |
| | ns.openPopupMenu(ev.inventor_event->getPosition()); |
| | return transit<NS::IdleState>(); |
| | } |
| | else { |
| | |
| | ns.setViewingMode(NavigationStyle::SELECTION); |
| | refire(); |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isPress(3)) { |
| | |
| | refire(); |
| | return transit<NS::IdleState>(); |
| | } |
| | if (ev.isMouseButtonEvent() ) { |
| | ns.postponedEvents.post(ev); |
| | } |
| | if (ev.isLocation2Event()) { |
| | auto mv = ev.inventor_event->getPosition() - this->base_pos; |
| | if (SbVec2f(mv).length() > ns.mouseMoveThreshold) { |
| | |
| | switch (ev.mbstate()) { |
| | case 0x100: { |
| | if (!long_click) { |
| | bool alt = ev.modifiers & NS::Event::ALTDOWN; |
| | bool allowSpin = alt == ns.is2DViewing(); |
| | if (allowSpin) { |
| | return transit<NS::RotateState>(); |
| | } |
| | else { |
| | refire(); |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | else { |
| | return transit<NS::StickyPanState>(); |
| | } |
| | } break; |
| | case 0x001: |
| | return transit<NS::PanState>(); |
| | break; |
| | case (0x101): |
| | return transit<NS::TiltState>(); |
| | break; |
| | default: |
| | |
| | refire(); |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | } |
| | if (ev.isGestureActive()) { |
| | ev.flags->processed = true; |
| | return transit<NS::GestureState>(); |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::RotateState: public sc::state<NS::RotateState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| |
|
| | public: |
| | explicit RotateState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | const auto inventorEvent |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event; |
| | ns.saveCursorPosition(inventorEvent); |
| | ns.setViewingMode(NavigationStyle::DRAGGING); |
| | this->base_pos = inventorEvent->getPosition(); |
| | if (ns.logging) { |
| | Base::Console().log(" -> RotateState\n"); |
| | } |
| | } |
| | virtual ~RotateState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.mbstate() == 0x101) { |
| | return transit<NS::TiltState>(); |
| | } |
| | if (ev.mbstate() == 0) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isLocation2Event()) { |
| | ev.flags->processed = true; |
| | SbVec2s pos = ev.inventor_event->getPosition(); |
| | auto& ns = this->outermost_context().ns; |
| | ns.spin_simplified(ns.normalizePixelPos(pos), ns.normalizePixelPos(this->base_pos)); |
| | this->base_pos = pos; |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::PanState: public sc::state<NS::PanState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| | float ratio; |
| |
|
| | public: |
| | explicit PanState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setViewingMode(NavigationStyle::PANNING); |
| | this->base_pos |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition(); |
| | if (ns.logging) { |
| | Base::Console().log(" -> PanState\n"); |
| | } |
| | this->ratio = ns.viewer->getSoRenderManager()->getViewportRegion().getViewportAspectRatio(); |
| | ns.setupPanningPlane(ns.viewer->getSoRenderManager()->getCamera()); |
| | } |
| | virtual ~PanState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.mbstate() == 0x101) { |
| | return transit<NS::TiltState>(); |
| | } |
| | if (ev.mbstate() == 0) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isLocation2Event()) { |
| | ev.flags->processed = true; |
| | SbVec2s pos = ev.inventor_event->getPosition(); |
| | auto& ns = this->outermost_context().ns; |
| | ns.panCamera( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | this->ratio, |
| | ns.panningplane, |
| | ns.normalizePixelPos(pos), |
| | ns.normalizePixelPos(this->base_pos) |
| | ); |
| | this->base_pos = pos; |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::StickyPanState: public sc::state<NS::StickyPanState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| | float ratio; |
| |
|
| | public: |
| | explicit StickyPanState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setViewingMode(NavigationStyle::PANNING); |
| | this->base_pos |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition(); |
| | if (ns.logging) { |
| | Base::Console().log(" -> StickyPanState\n"); |
| | } |
| | this->ratio = ns.viewer->getSoRenderManager()->getViewportRegion().getViewportAspectRatio(); |
| | ns.setupPanningPlane(ns.viewer->getSoRenderManager()->getCamera()); |
| | } |
| | virtual ~StickyPanState() |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.button2down = false; |
| | |
| | } |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.isRelease(1)) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isLocation2Event()) { |
| | ev.flags->processed = true; |
| | SbVec2s pos = ev.inventor_event->getPosition(); |
| | auto& ns = this->outermost_context().ns; |
| | ns.panCamera( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | this->ratio, |
| | ns.panningplane, |
| | ns.normalizePixelPos(pos), |
| | ns.normalizePixelPos(this->base_pos) |
| | ); |
| | this->base_pos = pos; |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::TiltState: public sc::state<NS::TiltState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| |
|
| | public: |
| | explicit TiltState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setRotationCenter(ns.getFocalPoint()); |
| | ns.setViewingMode(NavigationStyle::DRAGGING); |
| | this->base_pos |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition(); |
| | if (ns.logging) { |
| | Base::Console().log(" -> TiltState\n"); |
| | } |
| | ns.setupPanningPlane(ns.viewer->getSoRenderManager()->getCamera()); |
| | } |
| | virtual ~TiltState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.mbstate() == 0x001) { |
| | return transit<NS::PanState>(); |
| | } |
| | if (ev.mbstate() == 0x100) { |
| | return transit<NS::RotateState>(); |
| | } |
| | if (ev.mbstate() == 0) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isLocation2Event()) { |
| | ev.flags->processed = true; |
| | auto& ns = this->outermost_context().ns; |
| | SbVec2s pos = ev.inventor_event->getPosition(); |
| | float dx = (ns.normalizePixelPos(pos) - ns.normalizePixelPos(base_pos))[0]; |
| | ns.doRotate(ns.viewer->getSoRenderManager()->getCamera(), dx * (-2), SbVec2f(0.5, 0.5)); |
| | this->base_pos = pos; |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| |
|
| | class NS::GestureState: public sc::state<NS::GestureState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | private: |
| | SbVec2s base_pos; |
| | float ratio; |
| | bool enableTilt = false; |
| |
|
| | public: |
| | explicit GestureState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setViewingMode(NavigationStyle::PANNING); |
| | this->base_pos |
| | = static_cast<const NS::Event*>(this->triggering_event())->inventor_event->getPosition(); |
| | if (ns.logging) { |
| | Base::Console().log(" -> GestureState\n"); |
| | } |
| | ns.setupPanningPlane(ns.viewer->getSoRenderManager()->getCamera()); |
| | this->ratio = ns.viewer->getSoRenderManager()->getViewportRegion().getViewportAspectRatio(); |
| | enableTilt = !(App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetBool("DisableTouchTilt", true)); |
| | } |
| | virtual ~GestureState() |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | |
| | ns.button1down = false; |
| | ns.button2down = false; |
| | } |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.mbstate() == 0) { |
| | |
| | |
| | Base::Console().warning("leaving gesture state by mouse-click (fail-safe)\n"); |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | if (ev.isLocation2Event()) { |
| | |
| | |
| | ev.flags->processed = true; |
| | } |
| | if (ev.isGestureEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.asGestureEvent()->state == SoGestureEvent::SbGSEnd) { |
| | return transit<NS::IdleState>(); |
| | } |
| | else if (ev.asGestureEvent()->state == SoGestureEvent::SbGsCanceled) { |
| | |
| | return transit<NS::IdleState>(); |
| | |
| | |
| | } |
| | else if (ev.inventor_event->isOfType(SoGesturePanEvent::getClassTypeId())) { |
| | auto const& pangesture = static_cast<const SoGesturePanEvent*>(ev.inventor_event); |
| | SbVec2f panDist = ns.normalizePixelPos(pangesture->deltaOffset); |
| | ns.panCamera( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | ratio, |
| | ns.panningplane, |
| | panDist, |
| | SbVec2f(0, 0) |
| | ); |
| | } |
| | else if (ev.inventor_event->isOfType(SoGesturePinchEvent::getClassTypeId())) { |
| | const auto pinch = static_cast<const SoGesturePinchEvent*>(ev.inventor_event); |
| | SbVec2f panDist = ns.normalizePixelPos(pinch->deltaCenter.getValue()); |
| | ns.panCamera( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | ratio, |
| | ns.panningplane, |
| | panDist, |
| | SbVec2f(0, 0) |
| | ); |
| | ns.doZoom( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | -logf(float(pinch->deltaZoom)), |
| | ns.normalizePixelPos(pinch->curCenter) |
| | ); |
| | if (pinch->deltaAngle != 0.0 && enableTilt) { |
| | ns.doRotate( |
| | ns.viewer->getSoRenderManager()->getCamera(), |
| | float(pinch->deltaAngle), |
| | ns.normalizePixelPos(pinch->curCenter) |
| | ); |
| | } |
| | } |
| | else { |
| | |
| | ev.flags->processed = false; |
| | } |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::AwaitingReleaseState: public sc::state<NS::AwaitingReleaseState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | public: |
| | explicit AwaitingReleaseState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | if (ns.logging) { |
| | Base::Console().log(" -> AwaitingReleaseState\n"); |
| | } |
| | } |
| | virtual ~AwaitingReleaseState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| |
|
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = true; |
| | if (ev.mbstate() == 0) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| |
|
| | |
| | |
| | if (ev.mbstate() == 0x101) { |
| | if (ev.isPress(1)) { |
| | ns.rollDir = -1; |
| | } |
| | if (ev.isPress(2)) { |
| | ns.rollDir = +1; |
| | } |
| | } |
| | |
| | if ((ev.isRelease(1) && ev.mbstate() == 0x001) || (ev.isRelease(2) && ev.mbstate() == 0x100)) { |
| | ns.onRollGesture(ns.rollDir); |
| | } |
| |
|
| | if (ev.isLocation2Event()) { |
| | ev.flags->processed = true; |
| | } |
| | if (ev.isGestureActive()) { |
| | ev.flags->processed = true; |
| | |
| | return transit<NS::GestureState>(); |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | class NS::InteractState: public sc::state<NS::InteractState, NS::NaviMachine> |
| | { |
| | public: |
| | using reactions = sc::custom_reaction<NS::Event>; |
| |
|
| | public: |
| | explicit InteractState(my_context ctx) |
| | : my_base(ctx) |
| | { |
| | auto& ns = this->outermost_context().ns; |
| | ns.setViewingMode(NavigationStyle::INTERACT); |
| | if (ns.logging) { |
| | Base::Console().log(" -> InteractState\n"); |
| | } |
| | } |
| | virtual ~InteractState() = default; |
| |
|
| | sc::result react(const NS::Event& ev) |
| | { |
| | if (ev.isMouseButtonEvent()) { |
| | ev.flags->processed = false; |
| | if (ev.mbstate() == 0) { |
| | return transit<NS::IdleState>(); |
| | } |
| | } |
| | return forward_event(); |
| | } |
| | }; |
| |
|
| | |
| |
|
| |
|
| | |
| |
|
| | TYPESYSTEM_SOURCE(Gui::GestureNavigationStyle, Gui::UserNavigationStyle) |
| |
|
| |
|
| | GestureNavigationStyle::GestureNavigationStyle() |
| | : naviMachine(new NS::NaviMachine(*this)) |
| | , postponedEvents(*this) |
| | { |
| | this->logging = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetBool("NavigationDebug"); |
| | mouseMoveThreshold = QApplication::startDragDistance(); |
| | naviMachine->initiate(); |
| | } |
| |
|
| | GestureNavigationStyle::~GestureNavigationStyle() = default; |
| |
|
| | const char* GestureNavigationStyle::mouseButtons(ViewerMode mode) |
| | { |
| | switch (mode) { |
| | case NavigationStyle::SELECTION: |
| | return QT_TR_NOOP("Tap OR click left mouse button."); |
| | case NavigationStyle::PANNING: |
| | return QT_TR_NOOP("Drag screen with two fingers OR press right mouse button."); |
| | case NavigationStyle::DRAGGING: |
| | return QT_TR_NOOP( |
| | "Drag screen with one finger OR press left mouse button. In Sketcher and other " |
| | "edit modes, hold Alt in addition." |
| | ); |
| | case NavigationStyle::ZOOMING: |
| | return QT_TR_NOOP( |
| | "Pinch (place two fingers on the screen and drag them apart from or towards each " |
| | "other) OR scroll mouse wheel OR PgUp/PgDown on keyboard." |
| | ); |
| | default: |
| | return "No description"; |
| | } |
| | } |
| |
|
| |
|
| | SbBool GestureNavigationStyle::processSoEvent(const SoEvent* const ev) |
| | { |
| | |
| | |
| | |
| | if (this->isSeekMode()) { |
| | return superclass::processSoEvent(ev); |
| | } |
| | |
| | if (!this->isSeekMode() && !this->isAnimating() && this->isViewing()) { |
| | this->setViewing(false); |
| | } |
| |
|
| | NS::Event smev; |
| | smev.inventor_event = ev; |
| |
|
| | |
| | if (ev->isOfType(SoMotion3Event::getClassTypeId())) { |
| | smev.flags->processed = true; |
| | this->processMotionEvent(static_cast<const SoMotion3Event*>(ev)); |
| | return true; |
| | } |
| |
|
| | |
| | if (!viewer->isEditing()) { |
| | bool processed = handleEventInForeground(ev); |
| | if (processed) { |
| | return true; |
| | } |
| | } |
| |
|
| | if ((smev.isRelease(1) && !this->button1down) || (smev.isRelease(2) && !this->button2down) |
| | || (smev.isRelease(3) && !this->button3down)) { |
| | |
| | |
| | |
| | |
| | return true; |
| | } |
| |
|
| |
|
| | if (smev.isMouseButtonEvent()) { |
| | const int button = smev.asMouseButtonEvent()->getButton(); |
| | const SbBool press |
| | = smev.asMouseButtonEvent()->getState() == SoButtonEvent::DOWN ? true : false; |
| | switch (button) { |
| | case SoMouseButtonEvent::BUTTON1: |
| | this->button1down = press; |
| | break; |
| | case SoMouseButtonEvent::BUTTON2: |
| | this->button2down = press; |
| | break; |
| | case SoMouseButtonEvent::BUTTON3: |
| | this->button3down = press; |
| | break; |
| | |
| | } |
| | } |
| |
|
| | syncModifierKeys(ev); |
| |
|
| | smev.modifiers = (this->button1down ? NS::Event::BUTTON1DOWN : 0) |
| | | (this->button2down ? NS::Event::BUTTON2DOWN : 0) |
| | | (this->button3down ? NS::Event::BUTTON3DOWN : 0) |
| | | (this->ctrldown ? NS::Event::CTRLDOWN : 0) | (this->shiftdown ? NS::Event::SHIFTDOWN : 0) |
| | | (this->altdown ? NS::Event::ALTDOWN : 0); |
| |
|
| | #ifdef FC_OS_MACOSX |
| | |
| | |
| | |
| |
|
| | if (smev.isGestureEvent()) { |
| | return superclass::processSoEvent(ev); |
| | } |
| | #endif |
| |
|
| |
|
| | if (!smev.flags->processed) { |
| | this->naviMachine->processEvent(smev); |
| | } |
| | if (!smev.flags->propagated && !smev.flags->processed) { |
| | return superclass::processSoEvent(ev); |
| | } |
| | else { |
| | return smev.flags->processed; |
| | } |
| | } |
| |
|
| | SbBool GestureNavigationStyle::processSoEvent_bypass(const SoEvent* const ev) |
| | { |
| | return superclass::processSoEvent(ev); |
| | } |
| |
|
| | bool GestureNavigationStyle::is2DViewing() const |
| | { |
| | |
| | return this->viewer->isEditing(); |
| | } |
| |
|
| | void GestureNavigationStyle::onRollGesture(int direction) |
| | { |
| | std::string cmd; |
| | if (direction == +1) { |
| | if (logging) { |
| | Base::Console().log("Roll forward gesture\n"); |
| | } |
| | cmd = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetASCII("GestureRollFwdCommand"); |
| | } |
| | else if (direction == -1) { |
| | if (logging) { |
| | Base::Console().log("Roll backward gesture\n"); |
| | } |
| | cmd = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") |
| | ->GetASCII("GestureRollBackCommand"); |
| | } |
| | if (cmd.empty()) { |
| | return; |
| | } |
| | std::stringstream code; |
| | code << "Gui.runCommand(\"" << cmd << "\")"; |
| | try { |
| | Base::Interpreter().runString(code.str().c_str()); |
| | } |
| | catch (Base::PyException& exc) { |
| | exc.reportException(); |
| | } |
| | catch (...) { |
| | Base::Console().error( |
| | "GestureNavigationStyle::onRollGesture: unknown C++ exception when invoking command " |
| | "%s\n", |
| | cmd.c_str() |
| | ); |
| | } |
| | } |
| |
|
| | void GestureNavigationStyle::EventQueue::post(const NS::Event& ev) |
| | { |
| | ev.flags->processed = true; |
| | this->push(*ev.asMouseButtonEvent()); |
| | if (ns.logging) { |
| | Base::Console().log("postponed: "); |
| | ev.log(); |
| | } |
| | } |
| |
|
| | void GestureNavigationStyle::EventQueue::discardAll() |
| | { |
| | while (!this->empty()) { |
| | this->pop(); |
| | } |
| | } |
| |
|
| | void GestureNavigationStyle::EventQueue::forwardAll() |
| | { |
| | while (!this->empty()) { |
| | auto v = this->front(); |
| | this->ns.processSoEvent_bypass(&v); |
| | this->pop(); |
| | } |
| | } |
| |
|
| | } |
| |
|