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!

Nafffen

Two finger scrolling is working !
I was trying to implement the one finger scroll with your above code,
but I have a linker error on this function widgetBelowMouse->isDraggableWidget()isDraggableWidget's implementation seems to be missing in Widget.cpp ?

texus

The isDraggableWidget() function was removed. The return value of leftMousePressed was changed to bool, if it returns true then the event is a drag (and your code should do what it previously did if isDraggableWidget() returned true).

This is the information I posted about the change on Discord at the time, it contains most information you need to know:
QuoteThe way draggable widgets are implemented has been changed. No functionality was changed, so if you didn't write custom widgets then you don't need to care.

Draggable widgets are widgets that still receive mouse move events even when the mouse is no longer on top of them. For example, once you start dragging the thumb of a scrollbar, you can move the mouse outside the scrollbar and the thumb will continue to move up and down with the mouse.

Previously all widgets that needed such behavior would set a boolean flag in their constructor. Due to the way it was implemented, Container and all classes inheriting from it needed to be draggable as well. Custom widgets inheriting from SubwidgetContainer would act as draggable widgets (i.e. they receive no onMouseLeave callback while the left mouse button is pressed), whether they wanted this or not. In the new implementation, leftMousePressed will return a boolean based on whether the click initiated a potention drag or not.

Several breaking API changes were made to the Widget base class:
- isDraggableWidget() function and m_draggableWidget member have been removed
- Return type of leftMousePressed was changed from void to bool. True may be returned by draggable widgets, all other widgets will always return false.
- mousePressed function was removed (the function only existed for backwards-compatibility anyway, it called either leftMousePressed or rightMousePressed depending on the mouse button)