diff --git a/client/adventureMap/AdventureMapInterface.cpp b/client/adventureMap/AdventureMapInterface.cpp index 169b66778..d57e55e2a 100644 --- a/client/adventureMap/AdventureMapInterface.cpp +++ b/client/adventureMap/AdventureMapInterface.cpp @@ -54,7 +54,6 @@ AdventureMapInterface::AdventureMapInterface(): pos.x = pos.y = 0; pos.w = GH.screenDimensions().x; pos.h = GH.screenDimensions().y; - setMoveEventStrongInterest(true); // handle all mouse move events to prevent dead mouse move space in fullscreen mode shortcuts = std::make_shared(*this); diff --git a/client/adventureMap/CList.cpp b/client/adventureMap/CList.cpp index 320a88770..471ac2cfe 100644 --- a/client/adventureMap/CList.cpp +++ b/client/adventureMap/CList.cpp @@ -40,16 +40,13 @@ CList::CListItem::CListItem(CList * Parent) CList::CListItem::~CListItem() = default; -void CList::CListItem::wheelScrolled(int distance, bool inside) +void CList::CListItem::wheelScrolled(int distance) { - if (inside) - { - if (distance < 0) - parent->listBox->moveToNext(); - if (distance > 0) - parent->listBox->moveToPrev(); - parent->update(); - } + if (distance < 0) + parent->listBox->moveToNext(); + if (distance > 0) + parent->listBox->moveToPrev(); + parent->update(); } void CList::CListItem::clickRight(tribool down, bool previousState) diff --git a/client/adventureMap/CList.h b/client/adventureMap/CList.h index 27597c5ad..e511ce0da 100644 --- a/client/adventureMap/CList.h +++ b/client/adventureMap/CList.h @@ -35,7 +35,7 @@ protected: CListItem(CList * parent); ~CListItem(); - void wheelScrolled(int distance, bool inside) override; + void wheelScrolled(int distance) override; void clickRight(tribool down, bool previousState) override; void clickLeft(tribool down, bool previousState) override; void hover(bool on) override; diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 03d8b7259..45f63bf30 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -39,7 +39,6 @@ BattleFieldController::BattleFieldController(BattleInterface & owner): owner(owner) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; - setMoveEventStrongInterest(true); //preparing cells and hexes cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY); @@ -625,3 +624,10 @@ void BattleFieldController::show(Canvas & to) renderBattlefield(to); } + +bool BattleFieldController::receiveEvent(const Point & position, int eventType) const +{ + if (eventType == HOVER) + return true; + return CIntObject::receiveEvent(position, eventType); +} diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index 375f6540d..792eb2ed9 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -71,6 +71,8 @@ class BattleFieldController : public CIntObject void showAll(Canvas & to) override; void show(Canvas & to) override; void tick(uint32_t msPassed) override; + + bool receiveEvent(const Point & position, int eventType) const override; public: BattleFieldController(BattleInterface & owner); diff --git a/client/eventsSDL/InputSourceMouse.cpp b/client/eventsSDL/InputSourceMouse.cpp index 0d062d5f5..9fdda57a8 100644 --- a/client/eventsSDL/InputSourceMouse.cpp +++ b/client/eventsSDL/InputSourceMouse.cpp @@ -47,6 +47,9 @@ void InputSourceMouse::handleEventMouseButtonDown(const SDL_MouseButtonEvent & b case SDL_BUTTON_RIGHT: GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position); break; + case SDL_BUTTON_MIDDLE: + GH.events().dispatchGesturePanningStarted(position); + break; } } @@ -67,6 +70,9 @@ void InputSourceMouse::handleEventMouseButtonUp(const SDL_MouseButtonEvent & but case SDL_BUTTON_RIGHT: GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position); break; + case SDL_BUTTON_MIDDLE: + GH.events().dispatchGesturePanningEnded(); + break; } } diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp index f790724b8..2b7be246f 100644 --- a/client/eventsSDL/InputSourceTouch.cpp +++ b/client/eventsSDL/InputSourceTouch.cpp @@ -105,6 +105,7 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge { GH.input().setCursorPosition(convertTouchToMouse(tfinger)); lastTapPosition = GH.getCursorPosition(); + GH.events().dispatchGesturePanningStarted(lastTapPosition); state = TouchState::TAP_DOWN_SHORT; break; } @@ -152,6 +153,7 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger) } case TouchState::TAP_DOWN_PANNING: { + GH.events().dispatchGesturePanningEnded(); state = TouchState::IDLE; break; } @@ -160,7 +162,10 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger) if (SDL_GetNumTouchFingers(tfinger.touchId) == 1) state = TouchState::TAP_DOWN_PANNING; if (SDL_GetNumTouchFingers(tfinger.touchId) == 0) + { + GH.events().dispatchGesturePanningEnded(); state = TouchState::IDLE; + } break; } case TouchState::TAP_DOWN_LONG: diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 1d8e05631..dc738525c 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -229,7 +229,7 @@ void CIntObject::redraw() } } -bool CIntObject::isInside(const Point & position) +bool CIntObject::receiveEvent(const Point & position, int eventType) const { return pos.isInside(position); } diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 465f0f471..d6540242a 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -62,9 +62,6 @@ public: CIntObject(int used=0, Point offset=Point()); virtual ~CIntObject(); - //hover handling - void hover (bool on) override{} - //keyboard handling bool captureAllKeys; //if true, only this object should get info about pressed keys @@ -100,7 +97,7 @@ public: /// default behavior is to re-center, can be overriden void onScreenResize() override; - bool isInside(const Point & position) override; + bool receiveEvent(const Point & position, int eventType) const override; const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center diff --git a/client/gui/EventDispatcher.cpp b/client/gui/EventDispatcher.cpp index 7fca889f0..cfd4a52b3 100644 --- a/client/gui/EventDispatcher.cpp +++ b/client/gui/EventDispatcher.cpp @@ -134,7 +134,7 @@ void EventDispatcher::dispatchMouseDoubleClick(const Point & position) if(!vstd::contains(doubleClickInterested, i)) continue; - if(i->isInside(position)) + if(i->receiveEvent(position, AEventsReceiver::DOUBLECLICK)) { i->clickDouble(); doubleClicked = true; @@ -164,17 +164,21 @@ void EventDispatcher::handleMouseButtonClick(EventReceiversList & interestedObjs continue; auto prev = i->isMouseButtonPressed(btn); + if(!isPressed) i->currentMouseState[btn] = isPressed; - if(i->isInside(GH.getCursorPosition())) + + if( btn == MouseButton::LEFT && i->receiveEvent(GH.getCursorPosition(), AEventsReceiver::LCLICK)) { if(isPressed) i->currentMouseState[btn] = isPressed; - - if (btn == MouseButton::LEFT) - i->clickLeft(isPressed, prev); - if (btn == MouseButton::RIGHT) - i->clickRight(isPressed, prev); + i->clickLeft(isPressed, prev); + } + else if( btn == MouseButton::RIGHT && i->receiveEvent(GH.getCursorPosition(), AEventsReceiver::RCLICK)) + { + if(isPressed) + i->currentMouseState[btn] = isPressed; + i->clickRight(isPressed, prev); } else if(!isPressed) { @@ -196,7 +200,8 @@ void EventDispatcher::dispatchMouseScrolled(const Point & distance, const Point // ignore distance value and only provide its sign - we expect one scroll "event" to move sliders and such by 1 point, // and not by system-specific "number of lines to scroll", which is what 'distance' represents - i->wheelScrolled( std::clamp(distance.y, -1, 1) , i->isInside(position)); + if (i->receiveEvent(position, AEventsReceiver::WHEEL)) + i->wheelScrolled( std::clamp(distance.y, -1, 1)); } } @@ -216,11 +221,36 @@ void EventDispatcher::dispatchTextEditing(const std::string & text) } } +void EventDispatcher::dispatchGesturePanningStarted(const Point & initialPosition) +{ + for(auto it : panningInterested) + { + if (it->receiveEvent(initialPosition, AEventsReceiver::GESTURE_PANNING)) + { + it->panning(true); + it->panningState = true; + } + } +} + +void EventDispatcher::dispatchGesturePanningEnded() +{ + for(auto it : panningInterested) + { + if (it->isPanning()) + { + it->panning(false); + it->panningState = false; + } + } +} + void EventDispatcher::dispatchGesturePanning(const Point & distance) { for(auto it : panningInterested) { - it->gesturePanning(distance); + if (it->isPanning()) + it->gesturePanning(distance); } } @@ -231,7 +261,7 @@ void EventDispatcher::dispatchMouseMoved(const Point & position) auto hoverableCopy = hoverable; for(auto & elem : hoverableCopy) { - if(elem->isInside(position)) + if(elem->receiveEvent(position, AEventsReceiver::HOVER)) { if (!elem->isHovered()) { @@ -258,7 +288,7 @@ void EventDispatcher::dispatchMouseMoved(const Point & position) EventReceiversList miCopy = motioninterested; for(auto & elem : miCopy) { - if(elem->strongInterestState || elem->isInside(position)) //checking bounds including border fixes bug #2476 + if(elem->receiveEvent(position, AEventsReceiver::HOVER)) { (elem)->mouseMoved(position); } diff --git a/client/gui/EventDispatcher.h b/client/gui/EventDispatcher.h index 328022e03..2f0f2c519 100644 --- a/client/gui/EventDispatcher.h +++ b/client/gui/EventDispatcher.h @@ -62,7 +62,9 @@ public: void dispatchMouseDoubleClick(const Point & position); void dispatchMouseMoved(const Point & distance); - void dispatchGesturePanning(const Point & position); + void dispatchGesturePanningStarted(const Point & initialPosition); + void dispatchGesturePanningEnded(); + void dispatchGesturePanning(const Point & distance); /// Text input events void dispatchTextInput(const std::string & text); diff --git a/client/gui/EventsReceiver.cpp b/client/gui/EventsReceiver.cpp index e096d87ee..0cc0aa52b 100644 --- a/client/gui/EventsReceiver.cpp +++ b/client/gui/EventsReceiver.cpp @@ -17,7 +17,6 @@ AEventsReceiver::AEventsReceiver() : activeState(0) , hoveredState(false) - , strongInterestState(false) { } @@ -26,6 +25,11 @@ bool AEventsReceiver::isHovered() const return hoveredState; } +bool AEventsReceiver::isPanning() const +{ + return panningState; +} + bool AEventsReceiver::isActive() const { return activeState; @@ -36,11 +40,6 @@ bool AEventsReceiver::isMouseButtonPressed(MouseButton btn) const return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; } -void AEventsReceiver::setMoveEventStrongInterest(bool on) -{ - strongInterestState = on; -} - void AEventsReceiver::activateEvents(ui16 what) { assert((what & GENERAL) || (activeState & GENERAL)); diff --git a/client/gui/EventsReceiver.h b/client/gui/EventsReceiver.h index 7d188ff0d..f92f2729a 100644 --- a/client/gui/EventsReceiver.h +++ b/client/gui/EventsReceiver.h @@ -24,16 +24,12 @@ class AEventsReceiver { friend class EventDispatcher; + std::map currentMouseState; ui16 activeState; bool hoveredState; - bool strongInterestState; - std::map currentMouseState; + bool panningState; protected: - - /// If set, UI element will receive all mouse movement events, even those outside this element - void setMoveEventStrongInterest(bool on); - /// Activates particular events for this UI element. Uses unnamed enum from this class void activateEvents(ui16 what); /// Deactivates particular events for this UI element. Uses unnamed enum from this class @@ -43,11 +39,18 @@ protected: virtual void clickRight(tribool down, bool previousState) {} virtual void clickDouble() {} + /// Called when user pans screen by specified distance virtual void gesturePanning(const Point & distanceDelta) {} - virtual void wheelScrolled(int distance, bool inside) {} + + virtual void wheelScrolled(int distance) {} virtual void mouseMoved(const Point & cursorPosition) {} + + /// Called when UI element hover status changes virtual void hover(bool on) {} + /// Called when UI element panning gesture status changes + virtual void panning(bool on) {} + virtual void textInputed(const std::string & enteredText) {} virtual void textEdited(const std::string & enteredText) {} @@ -57,7 +60,9 @@ protected: virtual void tick(uint32_t msPassed) {} virtual bool captureThisKey(EShortcut key) = 0; - virtual bool isInside(const Point & position) = 0; + + /// If true, event of selected type in selected position will be processed by this element + virtual bool receiveEvent(const Point & position, int eventType) const= 0; public: AEventsReceiver(); @@ -69,6 +74,9 @@ public: /// Returns true if element is currently hovered by mouse bool isHovered() const; + /// Returns true if panning/swiping gesture is currently active + bool isPanning() const; + /// Returns true if element is currently active and may receive events bool isActive() const; diff --git a/client/lobby/OptionsTab.cpp b/client/lobby/OptionsTab.cpp index 7590d53ff..e7ea6cde0 100644 --- a/client/lobby/OptionsTab.cpp +++ b/client/lobby/OptionsTab.cpp @@ -48,6 +48,8 @@ OptionsTab::OptionsTab() : humanPlayers(0) if(SEL->screenType == ESelectionScreen::newGame || SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::scenarioInfo) { sliderTurnDuration = std::make_shared(Point(55, 551), 194, std::bind(&IServerAPI::setTurnLength, CSH, _1), 1, (int)GameConstants::POSSIBLE_TURNTIME.size(), (int)GameConstants::POSSIBLE_TURNTIME.size(), true, CSlider::BLUE); + sliderTurnDuration->setScrollBounds(Rect(-3, -25, 337, 43)); + sliderTurnDuration->setPanningStep(20); labelPlayerTurnDuration = std::make_shared(222, 538, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]); labelTurnDurationValue = std::make_shared(319, 559, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); } @@ -440,11 +442,8 @@ void OptionsTab::SelectedBox::clickRight(tribool down, bool previousState) } } -void OptionsTab::SelectedBox::wheelScrolled(int distance, bool isInside) +void OptionsTab::SelectedBox::wheelScrolled(int distance) { - if (!isInside) - return; - switch(CPlayerSettingsHelper::type) { case TOWN: diff --git a/client/lobby/OptionsTab.h b/client/lobby/OptionsTab.h index ad6132454..f03259b7e 100644 --- a/client/lobby/OptionsTab.h +++ b/client/lobby/OptionsTab.h @@ -100,7 +100,7 @@ public: SelectedBox(Point position, PlayerSettings & settings, SelType type); void clickRight(tribool down, bool previousState) override; - void wheelScrolled(int distance, bool isInside) override; + void wheelScrolled(int distance) override; void update(); }; diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index c28ae2dd6..c5bb49f21 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -205,6 +205,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type) labelTabTitle = std::make_shared(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle); slider = std::make_shared(Point(372, 86), tabType != ESelectionScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE); + slider->setPanningStep(24); filter(0); } diff --git a/client/mapView/MapViewActions.cpp b/client/mapView/MapViewActions.cpp index 7cec9dac9..a9d1f3ac7 100644 --- a/client/mapView/MapViewActions.cpp +++ b/client/mapView/MapViewActions.cpp @@ -64,10 +64,8 @@ void MapViewActions::mouseMoved(const Point & cursorPosition) handleHover(cursorPosition); } -void MapViewActions::wheelScrolled(int distance, bool inside) +void MapViewActions::wheelScrolled(int distance) { - if (!inside) - return; adventureInt->hotkeyZoom(distance); } diff --git a/client/mapView/MapViewActions.h b/client/mapView/MapViewActions.h index 03e85b110..d2c4576f5 100644 --- a/client/mapView/MapViewActions.h +++ b/client/mapView/MapViewActions.h @@ -34,5 +34,5 @@ public: void gesturePanning(const Point & distance) override; void hover(bool on) override; void mouseMoved(const Point & cursorPosition) override; - void wheelScrolled(int distance, bool inside) override; + void wheelScrolled(int distance) override; }; diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index c542f9899..bfdfc86e6 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -619,22 +619,69 @@ void CSlider::clickLeft(tribool down, bool previousState) removeUsedEvents(MOVE); } +bool CSlider::receiveEvent(const Point &position, int eventType) const +{ + if (eventType != WHEEL && eventType != GESTURE_PANNING) + { + return CIntObject::receiveEvent(position, eventType); + } + + if (!scrollBounds) + return true; + + Rect testTarget = *scrollBounds + pos.topLeft(); + + return testTarget.isInside(position); +} + +void CSlider::setPanningStep(int to) +{ + panningDistanceSingle = to; +} + +void CSlider::panning(bool on) +{ + panningDistanceAccumulated = 0; +} + +void CSlider::gesturePanning(const Point & distanceDelta) +{ + if (horizontal) + panningDistanceAccumulated += -distanceDelta.x; + else + panningDistanceAccumulated += distanceDelta.y; + + if (-panningDistanceAccumulated > panningDistanceSingle ) + { + int scrollAmount = (-panningDistanceAccumulated) / panningDistanceSingle; + moveBy(-scrollAmount); + panningDistanceAccumulated += scrollAmount * panningDistanceSingle; + } + + if (panningDistanceAccumulated > panningDistanceSingle ) + { + int scrollAmount = panningDistanceAccumulated / panningDistanceSingle; + moveBy(scrollAmount); + panningDistanceAccumulated += -scrollAmount * panningDistanceSingle; + } +} + CSlider::CSlider(Point position, int totalw, std::function Moved, int Capacity, int Amount, int Value, bool Horizontal, CSlider::EStyle style) - : CIntObject(LCLICK | RCLICK | WHEEL), + : CIntObject(LCLICK | RCLICK | WHEEL | GESTURE_PANNING ), capacity(Capacity), horizontal(Horizontal), amount(Amount), value(Value), scrollStep(1), - moved(Moved) + moved(Moved), + panningDistanceAccumulated(0), + panningDistanceSingle(32) { OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); setAmount(amount); vstd::amax(value, 0); vstd::amin(value, positions); - setMoveEventStrongInterest(true); - pos.x += position.x; pos.y += position.y; @@ -707,16 +754,8 @@ void CSlider::showAll(Canvas & to) CIntObject::showAll(to); } -void CSlider::wheelScrolled(int distance, bool in) +void CSlider::wheelScrolled(int distance) { - if (scrollBounds) - { - Rect testTarget = *scrollBounds + pos.topLeft(); - - if (!testTarget.isInside(GH.getCursorPosition())) - return; - } - // vertical slider -> scrolling up move slider upwards // horizontal slider -> scrolling up moves slider towards right bool positive = ((distance < 0) != horizontal); diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h index 0c52ad9e9..7bb1d83eb 100644 --- a/client/widgets/Buttons.h +++ b/client/widgets/Buttons.h @@ -192,12 +192,24 @@ class CSlider : public CIntObject std::optional scrollBounds; - int capacity;//how many elements can be active at same time (e.g. hero list = 5) - int positions; //number of highest position (0 if there is only one) + /// how many elements are visible simultaneously + int capacity; + /// number of highest position, or 0 if there is only one + int positions; + /// if true, then slider is not vertical but horizontal bool horizontal; - int amount; //total amount of elements (e.g. hero list = 0-8) - int value; //first active element - int scrollStep; // how many elements will be scrolled via one click, default = 1 + /// total amount of elements in the list + int amount; + /// topmost vislble (first active) element + int value; + /// how many elements will be scrolled via one click, default = 1 + int scrollStep; + + /// How far player must move finger/mouse to move slider by 1 via gesture + int panningDistanceSingle; + /// How far have player moved finger/mouse via gesture so far. + int panningDistanceAccumulated; + CFunctionList moved; void updateSliderPos(); @@ -215,6 +227,9 @@ public: /// Controls how many items wil be scrolled via one click void setScrollStep(int to); + /// Controls size of panning step needed to move list by 1 item + void setPanningStep(int to); + /// If set, mouse scroll will only scroll slider when inside of this area void setScrollBounds(const Rect & bounds ); void clearScrollBounds(); @@ -237,11 +252,15 @@ public: void addCallback(std::function callback); + bool receiveEvent(const Point & position, int eventType) const override; + void keyPressed(EShortcut key) override; - void wheelScrolled(int distance, bool in) override; + void wheelScrolled(int distance) override; + void gesturePanning(const Point & distanceDelta) override; void clickLeft(tribool down, bool previousState) override; void mouseMoved (const Point & cursorPosition) override; void showAll(Canvas & to) override; + void panning(bool on) override; /// @param position coordinates of slider /// @param length length of slider ribbon, including left/right buttons diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp index 335708e43..d669dd348 100644 --- a/client/widgets/ObjectLists.cpp +++ b/client/widgets/ObjectLists.cpp @@ -94,6 +94,8 @@ CListBox::CListBox(CreateFunc create, Point Pos, Point ItemOffset, size_t Visibl OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE); slider = std::make_shared(SliderPos.topLeft(), SliderPos.w, std::bind(&CListBox::moveToPos, this, _1), (int)VisibleSize, (int)TotalSize, (int)InitialPos, Slider & 2, Slider & 4 ? CSlider::BLUE : CSlider::BROWN); + + slider->setPanningStep(itemOffset.x + itemOffset.y); } reset(); }