behavior on phone

Started by Nafffen, 05 March 2023, 13:58:08

Nafffen

I noticed on home page that TGUI is ok with Android/IOS (I'm with SFML backend so I didnt have a doubt about that). I haven't tested yet, before so, I have some questions to help me organize myself:
- Do TGUI have differents behavior with some events, due the usage of fingers ? For example onHover event
- How scrolling is handled ? Do we need to pinch two fingers or there is something else ?
- With scrollPanel, can we drag finger anywhere on the panel to move it or do we must drag only the bar ? If only the bar, what is the best way to have the first behavior ?
Thank you

texus

TGUI isn't optimized for touch devices at all. While it can be used on Android and iOS, it provides you with the same behavior as on a desktop. Touch events are supported, but they are internally translated to mouse events before being passed to the widgets.

SFML also provides limited support for Android and iOS.
First of all you will need to use the 2.6.x branch, as Android support in SFML 2.5.1 is pretty broken.
Secondly, there is a serious limitation with the SFML backend related to the software keyboard. Touching an edit box will bring up the software keyboard on your phone/tablet, but the OS has no idea where the edit box is located on your screen. So the keyboard can appear on top of the edit box and hide it. When using the SDL_RENDERER backend, TGUI has the ability to tell SDL where the edit box is placed, and the view will automatically be shifted to keep the edit box visible while typing.

Quote- Do TGUI have differents behavior with some events, due the usage of fingers ? For example onHover event
Everything is the same except for touch events, which work like this:
- Any fingers other than the first one are ignored.
- The finger down event is translated to a MousePressed event (and a MouseMove event that is send right before the MousePressed event is send).
- While the finger is down, finger motion events are translated to a MouseMoved events.
- The finger up event in translated to a MouseReleased event.

Quote- How scrolling is handled ? Do we need to pinch two fingers or there is something else ?
Quote- With scrollPanel, can we drag finger anywhere on the panel to move it or do we must drag only the bar ? If only the bar, what is the best way to have the first behavior ?
You have to use the scrollbar.

Using only one finger to drag the panel could probably be implemented relatively easily, but it wouldn't work when the finger goes down on top of a widget inside the panel (e.g. press and drag in edit box would start selecting text instead of scrolling the panel).

I'm not sure what can be done to get two finger scrolling working. Manually examining touch events to extract gestures doesn't sound easy. Apparently SDL has a SDL_MultiGesture event for this which I could in theory translate to a scroll event when two fingers are used (but doing so might not be as straightforward as it sounds). That won't help you when using SFML though, as it has no support for touch gestures at all (it just provides you with information about which finger is located where).

Nafffen

Ok thank you very much for this information ! Very useful to know.

QuoteUsing only one finger to drag the panel could probably be implemented relatively easily, but it wouldn't work when the finger goes down on top of a widget inside the panel (e.g. press and drag in edit box would start selecting text instead of scrolling the panel).
I want a behavior like this:
When first press a finger on the scrollPanel, nothing append (even if finger is on a child widget)
If finger is moved, the panel scrolls
Else if finger is released, the "click" is spread through child widgets

Can I achieve that more or less easily with a custom widget inherited by tgui::ScrollPanel ?

texus

#3
It can definitely be done.
I quickly implemented it myself to demonstrate it, but you will need to get the latest TGUI version as I had to make the scrollbars in ScrollablePanel protected instead of private in order to compile this code.
Code (cpp) Select
class CustomScrollablePanel : public tgui::ScrollablePanel
{
public:
    typedef std::shared_ptr<CustomScrollablePanel> Ptr;
    typedef std::shared_ptr<const CustomScrollablePanel> ConstPtr;

    CustomScrollablePanel(const char* typeName = "CustomScrollablePanel", bool initRenderer = true) :
        tgui::ScrollablePanel(typeName, initRenderer)
    {
    }

    void leftMousePressed(tgui::Vector2f pos) override
    {
        m_mouseDown = true;

        if (m_verticalScrollbar->isMouseOnWidget(pos - getPosition()))
            m_verticalScrollbar->leftMousePressed(pos - getPosition());
        else if (m_horizontalScrollbar->isMouseOnWidget(pos - getPosition()))
            m_horizontalScrollbar->leftMousePressed(pos - getPosition());
        else if (tgui::FloatRect{getPosition().x + getChildWidgetsOffset().x, getPosition().y + getChildWidgetsOffset().y, getInnerSize().x, getInnerSize().y}.contains(pos))
        {
            m_dragging = true;
            m_originalDragPos = pos;
            m_dragPos = pos;
        }
    }

    void leftMouseReleased(tgui::Vector2f pos) override
    {
        if (m_dragging)
        {
            m_dragging = false;

            if (std::abs(m_originalDragPos.x - pos.x) <= 2 && std::abs(m_originalDragPos.y - pos.y) <= 2)
            {
                if ((!m_verticalScrollbar->isMouseOnWidget(pos - getPosition()) && !m_horizontalScrollbar->isMouseOnWidget(pos - getPosition()))
                 && tgui::FloatRect{getPosition().x + getChildWidgetsOffset().x, getPosition().y + getChildWidgetsOffset().y, getInnerSize().x, getInnerSize().y}.contains(pos))
                {
                    ScrollablePanel::leftMousePressed({pos.x + static_cast<float>(m_horizontalScrollbar->getValue()),
                                                       pos.y + static_cast<float>(m_verticalScrollbar->getValue())});
                    ScrollablePanel::leftMouseReleased({pos.x + static_cast<float>(m_horizontalScrollbar->getValue()),
                                                        pos.y + static_cast<float>(m_verticalScrollbar->getValue())});
                }
            }

            return;
        }

        ScrollablePanel::leftMouseReleased(pos);
    }

    void mouseMoved(tgui::Vector2f pos) override
    {
        if (m_dragging)
        {
            const float delta = (pos.y - m_dragPos.y) / static_cast<float>(m_verticalScrollbar->getScrollAmount());
            m_verticalScrollbar->mouseWheelScrolled(delta, pos);
            m_dragPos = pos;
            return;
        }

        ScrollablePanel::mouseMoved(pos);
    }

    void leftMouseButtonNoLongerDown() override
    {
        m_dragging = false;
        ScrollablePanel::leftMouseButtonNoLongerDown();
    }

    static CustomScrollablePanel::Ptr create()
    {
        return std::make_shared<CustomScrollablePanel>();
    }

    static CustomScrollablePanel::Ptr copy(CustomScrollablePanel::ConstPtr widget)
    {
        if (widget)
            return std::static_pointer_cast<CustomScrollablePanel>(widget->clone());
        else
            return nullptr;
    }

protected:

    Widget::Ptr clone() const override
    {
        return std::make_shared<CustomScrollablePanel>(*this);
    }

private:
    bool m_dragging = false;
    tgui::Vector2f m_originalDragPos;
    tgui::Vector2f m_dragPos;
};

This method has noticeable downsides though. It breaks dragging in widgets, so you can't e.g. have a slider inside the panel. You also e.g. don't get a visual feedback on button presses: because the click happens instantly, you won't see the button go to down state. This actually caused me to believe the code wasn't working for a moment until I realized it and connected an onPress callback to properly test the button press.

Nafffen

Ok thank you once again ! Very interesting
QuoteThis method has noticeable downsides though. It breaks dragging in widgets, so you can't e.g. have a slider inside the panel.
Is there a way to have no problem with dragging widgets ?
If a reuse the behavior I wanted to handle dragging widgets it would be:
When first press a finger on the scrollPanel, nothing append (even if finger is on a child widget)
If finger is moved:
   If the child widget under the mouse is not a dragging widget, the panel scrolls
   Else the event is spread through the dragging widget
Else if finger is released, the "click" is spread through child widgets
It is not very clear sorry

texus

#5
Doing it like that is indeed better. If you replace the leftMousePressed function in my previous post with the following then draggable widgets would be supported.
Code (cpp) Select
void leftMousePressed(tgui::Vector2f pos) override
{
    m_mouseDown = true;

    if (m_verticalScrollbar->isMouseOnWidget(pos - getPosition()))
        m_verticalScrollbar->leftMousePressed(pos - getPosition());
    else if (m_horizontalScrollbar->isMouseOnWidget(pos - getPosition()))
        m_horizontalScrollbar->leftMousePressed(pos - getPosition());
    else if (tgui::FloatRect{getPosition().x + getChildWidgetsOffset().x, getPosition().y + getChildWidgetsOffset().y, getInnerSize().x, getInnerSize().y}.contains(pos))
    {
        auto widgetBelowMouse = getWidgetBelowMouse(pos - getPosition() - getChildWidgetsOffset());
        if (widgetBelowMouse && widgetBelowMouse->isDraggableWidget())
            ScrollablePanel::leftMousePressed(pos);
        else
        {
            m_dragging = true;
            m_originalDragPos = pos;
            m_dragPos = pos;
        }
    }
}

When I find some time in the next few weeks I will investigate how much work it would be to get two-finger scrolling working. It would be a much better option if I can get it to work.

texus

Two finger scrolling should now work out of the box in the latest TGUI version!