From c71b526b72223493a46f8ffc0ae9f8ea06fc08ce Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 23 Apr 2025 13:01:49 +0200 Subject: [PATCH] Gui: Refactor navigation styles Add new base class for state chart based navigation styles --- src/Gui/CMakeLists.txt | 2 + src/Gui/Navigation/NavigationStateChart.cpp | 233 ++++++++++++++++++++ src/Gui/Navigation/NavigationStateChart.h | 148 +++++++++++++ src/Gui/SoFCDB.cpp | 2 + 4 files changed, 385 insertions(+) create mode 100644 src/Gui/Navigation/NavigationStateChart.cpp create mode 100644 src/Gui/Navigation/NavigationStateChart.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index d6b7d6850d..f793a48ebd 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -932,6 +932,7 @@ SET(View3D_SRCS SOURCE_GROUP("View3D" FILES ${View3D_SRCS}) SET(Navigation_CPP_SRCS + Navigation/NavigationStateChart.cpp Navigation/NavigationStyle.cpp Navigation/NavigationStylePyImp.cpp Navigation/InventorNavigationStyle.cpp @@ -951,6 +952,7 @@ SET(Navigation_CPP_SRCS ) SET(Navigation_SRCS ${Navigation_CPP_SRCS} + Navigation/NavigationStateChart.h Navigation/NavigationStyle.h Navigation/NavigationStyle.pyi Navigation/GestureNavigationStyle.h diff --git a/src/Gui/Navigation/NavigationStateChart.cpp b/src/Gui/Navigation/NavigationStateChart.cpp new file mode 100644 index 0000000000..c146fbb9a6 --- /dev/null +++ b/src/Gui/Navigation/NavigationStateChart.cpp @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2025 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" +#include +#include +#include + +#include "Camera.h" +#include "NavigationStateChart.h" +#include "View3DInventorViewer.h" + + +using namespace Gui; +namespace sc = boost::statechart; +using NS = NavigationStateChart; + +NS::Event::Event() : flags(new Flags) +{} + +bool NS::Event::isMouseButtonEvent() const +{ + return this->inventor_event->isOfType(SoMouseButtonEvent::getClassTypeId()); +} + +const SoMouseButtonEvent* NS::Event::asMouseButtonEvent() const +{ + return static_cast(this->inventor_event); +} + +bool NS::Event::isPress(Button button) const +{ + return SoMouseButtonEvent::isButtonPressEvent(this->inventor_event, button); +} + +bool NS::Event::isRelease(Button button) const +{ + return SoMouseButtonEvent::isButtonReleaseEvent(this->inventor_event, button); +} + +bool NS::Event::isKeyPress(Key key) const +{ + return SoKeyboardEvent::isKeyPressEvent(this->inventor_event, key); +} + +bool NS::Event::isKeyRelease(Key key) const +{ + return SoKeyboardEvent::isKeyReleaseEvent(this->inventor_event, key); +} + +bool NS::Event::isKeyboardEvent() const +{ + return this->inventor_event->isOfType(SoKeyboardEvent::getClassTypeId()); +} + +const SoKeyboardEvent* NS::Event::asKeyboardEvent() const +{ + return static_cast(this->inventor_event); +} + +bool NS::Event::isLocation2Event() const +{ + return this->inventor_event->isOfType(SoLocation2Event::getClassTypeId()); +} + +const SoLocation2Event* NS::Event::asLocation2Event() const +{ + return static_cast(this->inventor_event); +} + +bool NS::Event::isMotion3Event() const +{ + return this->inventor_event->isOfType(SoMotion3Event::getClassTypeId()); +} + +const SoMotion3Event* NS::Event::asMotion3Event() const +{ + return static_cast(this->inventor_event); +} + +bool NS::Event::isDownButton(unsigned int state) const +{ + return mbstate() == state; +} + +bool NS::Event::isDownNoButton() const +{ + return mbstate() == 0; +} + +bool NS::Event::isDownButton1() const +{ + return (mbstate() & BUTTON1DOWN) == BUTTON1DOWN; +} + +bool NS::Event::isDownButton2() const +{ + return (mbstate() & BUTTON2DOWN) == BUTTON2DOWN; +} + +bool NS::Event::isDownButton3() const +{ + return (mbstate() & BUTTON3DOWN) == BUTTON3DOWN; +} + +bool NS::Event::isDownControl() const +{ + return (kbstate() & CTRLDOWN) == CTRLDOWN; +} + +bool NS::Event::isDownShift() const +{ + return (kbstate() & SHIFTDOWN) == SHIFTDOWN; +} + +bool NS::Event::isDownAlt() const +{ + return (kbstate() & ALTDOWN) == ALTDOWN; +} + + +/* TRANSLATOR Gui::NavigationStateChart */ + +TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStateChart, Gui::UserNavigationStyle) + +NavigationStateChart::NavigationStateChart() + : naviMachine(new NS::StateMachine()) +{ +} + +NavigationStateChart::~NavigationStateChart() +{ + naviMachine.reset(); +} + +SbBool NavigationStateChart::processSoEvent(const SoEvent * const ev) +{ + // Events when in "ready-to-seek" mode are ignored, except those + // which influence the seek mode itself -- these are handled further + // up the inheritance hierarchy. + if (this->isSeekMode()) { + return inherited::processSoEvent(ev); + } + + // Switch off viewing mode (Bug #0000911) + if (!this->isSeekMode() && !this->isAnimating() && this->isViewing()) { + this->setViewing(false); // by default disable viewing mode to render the scene + } + + // Mismatches in state of the modifier keys happens if the user + // presses or releases them outside the viewer window. + syncModifierKeys(ev); + + // give the nodes in the foreground root the chance to handle events (e.g color bar) + if (!viewer->isEditing()) { + if (handleEventInForeground(ev)) { + return true; + } + } + + NS::Event smev; + smev.inventor_event = ev; + + // Spaceball/joystick handling + if (ev->isOfType(SoMotion3Event::getClassTypeId())){ + smev.flags->processed = true; + this->processMotionEvent(static_cast(ev)); + return true; + } + + // Keyboard handling + if (ev->isOfType(SoKeyboardEvent::getClassTypeId())) { + const auto event = static_cast(ev); + smev.flags->processed = processKeyboardEvent(event); + } + + if (smev.isMouseButtonEvent()) { + const auto button = smev.asMouseButtonEvent()->getButton(); + const SbBool press = smev.asMouseButtonEvent()->getState() == SoButtonEvent::DOWN; + switch (button) { + case SoMouseButtonEvent::BUTTON1: + this->button1down = press; + break; + case SoMouseButtonEvent::BUTTON2: + this->button2down = press; + break; + case SoMouseButtonEvent::BUTTON3: + this->button3down = press; + break; + default: + break; + } + } + + 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); + + if (!smev.flags->processed) { + this->naviMachine->process_event(smev); + } + + if (!smev.flags->propagated && !smev.flags->processed) { + return inherited::processSoEvent(ev); + } + + return smev.flags->processed; +} diff --git a/src/Gui/Navigation/NavigationStateChart.h b/src/Gui/Navigation/NavigationStateChart.h new file mode 100644 index 0000000000..f7e7a50008 --- /dev/null +++ b/src/Gui/Navigation/NavigationStateChart.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2025 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef GUI_NAVIGATIONSTATECHART_H +#define GUI_NAVIGATIONSTATECHART_H + +#include +#include + +// NOLINTBEGIN(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls) +namespace Gui +{ + +class GuiExport NavigationStateChart : public UserNavigationStyle { + using inherited = UserNavigationStyle; + + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + struct Event: public boost::statechart::event + { + using Button = SoMouseButtonEvent::Button; + using Key = SoKeyboardEvent::Key; + + Event(); + bool isMouseButtonEvent() const; + const SoMouseButtonEvent* asMouseButtonEvent() const; + bool isPress(Button button) const; + bool isRelease(Button button) const; + bool isKeyPress(Key key) const; + bool isKeyRelease(Key key) const; + bool isKeyboardEvent() const; + const SoKeyboardEvent* asKeyboardEvent() const; + bool isLocation2Event() const; + const SoLocation2Event* asLocation2Event() const; + bool isMotion3Event() const; + const SoMotion3Event* asMotion3Event() const; + bool isDownButton(unsigned int state) const; + bool isDownNoButton() const; + bool isDownButton1() const; + bool isDownButton2() const; + bool isDownButton3() const; + bool isDownControl() const; + bool isDownShift() const; + bool isDownAlt() const; + + enum { + // bits: 0-shift-ctrl-alt-0-lmb-mmb-rmb + BUTTON1DOWN = 0x00000100, + BUTTON2DOWN = 0x00000001, + BUTTON3DOWN = 0x00000010, + CTRLDOWN = 0x00100000, + SHIFTDOWN = 0x01000000, + ALTDOWN = 0x00010000, + MASKBUTTONS = BUTTON1DOWN | BUTTON2DOWN | BUTTON3DOWN, + MASKMODIFIERS = CTRLDOWN | SHIFTDOWN | ALTDOWN + }; + + const SoEvent* inventor_event{nullptr}; + unsigned int modifiers{0}; + unsigned int mbstate() const {return modifiers & MASKBUTTONS;} + unsigned int kbstate() const {return modifiers & MASKMODIFIERS;} + + struct Flags{ + bool processed = false; + bool propagated = false; + }; + std::shared_ptr flags; + }; + + class StateMachine + { + public: + template + void make_object(T* obj) + { + object = std::make_shared>(obj); + } + + void process_event(const Event& ev) + { + if (object) { + object->process_event(ev); + } + } + + struct Concept + { + virtual ~Concept() = default; + virtual void process_event(const Event&) = 0; + }; + + template< typename T > + struct Model : Concept + { + explicit Model(T* t) : object(t) + { + object->initiate(); + } + ~Model() override + { + object.reset(); + } + void process_event(const Event& ev) override + { + object->process_event(ev); + } + + private: + std::shared_ptr object; + }; + + std::shared_ptr object; + }; + + NavigationStateChart(); + ~NavigationStateChart() override; + +protected: + SbBool processSoEvent(const SoEvent * const ev) override; + std::unique_ptr naviMachine; // NOLINT +}; + +} // namespace Gui +// NOLINTEND(cppcoreguidelines-avoid*, readability-avoid-const-params-in-decls) + +#endif // GUI_NAVIGATIONSTATECHART_H diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 11e1a76212..1e9e927075 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -53,6 +53,7 @@ #include "Inventor/Draggers/SoTransformDragger.h" #include "Navigation/GestureNavigationStyle.h" #include "Navigation/NavigationStyle.h" +#include "Navigation/NavigationStateChart.h" #include "SelectionObject.h" #include "SoDevicePixelRatioElement.h" #include "SoFCColorBar.h" @@ -188,6 +189,7 @@ void Gui::SoFCDB::init() NavigationStyle ::init(); UserNavigationStyle ::init(); + NavigationStateChart ::init(); InventorNavigationStyle ::init(); CADNavigationStyle ::init(); RevitNavigationStyle ::init();