Browse Source

Implemented panning/swiping gesture for sliders

Ivan Savenko 2 years ago
parent
commit
360bf48031

+ 0 - 1
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<AdventureMapShortcuts>(*this);
 

+ 6 - 9
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)

+ 1 - 1
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;

+ 7 - 1
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);
+}

+ 2 - 0
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);
 

+ 6 - 0
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;
 	}
 }
 

+ 5 - 0
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:

+ 1 - 1
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);
 }

+ 1 - 4
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

+ 41 - 11
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);
 		}

+ 3 - 1
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);

+ 5 - 6
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));

+ 16 - 8
client/gui/EventsReceiver.h

@@ -24,16 +24,12 @@ class AEventsReceiver
 {
 	friend class EventDispatcher;
 
+	std::map<MouseButton, bool> currentMouseState;
 	ui16 activeState;
 	bool hoveredState;
-	bool strongInterestState;
-	std::map<MouseButton, bool> 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;
 

+ 3 - 4
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<CSlider>(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<CLabel>(222, 538, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]);
 		labelTurnDurationValue = std::make_shared<CLabel>(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:

+ 1 - 1
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();
 	};

+ 1 - 0
client/lobby/SelectionTab.cpp

@@ -205,6 +205,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 
 	labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle);
 	slider = std::make_shared<CSlider>(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);
 }
 

+ 1 - 3
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);
 }
 

+ 1 - 1
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;
 };

+ 52 - 13
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<void(int)> 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);

+ 25 - 6
client/widgets/Buttons.h

@@ -192,12 +192,24 @@ class CSlider : public CIntObject
 
 	std::optional<Rect> 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<void(int)> 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<void(int)> 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

+ 2 - 0
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<CSlider>(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();
 }