1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

Implemented panning/swiping gesture for sliders

This commit is contained in:
Ivan Savenko 2023-05-29 13:08:08 +03:00
parent 2a30eccb2d
commit 360bf48031
21 changed files with 180 additions and 71 deletions

View File

@ -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);

View File

@ -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();
}
}
void CList::CListItem::clickRight(tribool down, bool previousState)

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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);
}

View File

@ -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

View File

@ -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,16 +164,20 @@ 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)
}
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,10 +221,35 @@ 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)
{
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);
}

View File

@ -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);

View File

@ -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));

View File

@ -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;

View File

@ -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:

View File

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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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

View File

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