Improving PopupMenu for touch screens

Hi,

PopupMenu, especially when there are submenus involved , do not work well with touchscreens, either on iOS or on Windows: the submenus often fail to open, the menu is sometimes inadvertently closed. With long menus, the auto scroll does not work well (it is really designed for mouses, by depending on the current mouse position)

Here is an attempt at improving the situation for iOS – with this patch PopupMenu work much better on iOS – not perfect but really better. The first thing that I change is to remove MouseInputSource::offscreenMousePos from juce_iOS_UIViewComponentPeer:

if (isUp || isCancel)
{
    handleMouseEvent (MouseInputSource::InputSourceType::touch, pos /* MouseInputSource::offscreenMousePos*/, modsToSend,
                      MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, touchIndex);

It does not seem to break anything (appart from the auto-scroll of popupmenus, but it was already not working well). With this change the PopupMenu MouseInputSource will stop receiving fake mousemove events that prevent submenus from working. So this simple change improves a lot of things already.

Now, I what to also be able to scroll in the menu , just by dragging. I came up with this patch:

double scrollAcceleration = 0;
uint32 lastScrollTime, lastMouseMoveTime = 0;
bool isDown = false;
bool isTouchDragging = false; Point<int> lastTouchDraggingPos; // ADD THIS

void handleMousePosition (Point<int> globalMousePos)
{
    auto localMousePos = window.getLocalPoint (nullptr, globalMousePos);
    auto timeNow = Time::getMillisecondCounter();
   /// BEGIN ADD THIS
    if (source.isTouch()) {
      if (source.getIndex() > 0) return; // one finger only

      if (source.isDragging() && source.hasMovedSignificantlySincePressed()) {
        bool move = isTouchDragging && window.canScroll();
        // using globalMousePos does not work well when scrolling (jumps)
        auto pos = source.getScreenPosition().roundToInt();

        if (move) {
            auto delta_y = lastTouchDraggingPos.y - pos.y;
            if (delta_y) {
                window.alterChildYPos(delta_y);
            }
        }
        lastTouchDraggingPos = pos;
        isTouchDragging = true;
      }
      if (!source.isDragging() && isTouchDragging) {
        isTouchDragging = false;
        isDown = false; // prevent triggerCurrentlyHighlightedItem from being called after a drag
    }
    if (isTouchDragging) {
        if (window.activeSubMenu)
            window.activeSubMenu->hide (nullptr, true);
        return; // skip everything else (the auto-scroll, the submenu display..)
    }
  }
  // END ADD THIS

if (timeNow > window.timeEnteredCurrentChildComp + 100
     && window.reallyContains (localMousePos, true)

It’s still not working on windows, I assume that the situation where there is both a mouse and a touchscreen is causing specific issues.

I really would like the PopupMenu to be working better with touchscreens , this is a complicated but so useful component…

2 Likes

Here is an updated patch:

popup_menu_submenu_fix_and_improvements_on_ios.patch

submenus were still quite broken, when there was overlapping on iOS (when the submenu covers part of its parent menu, especially when submenus have submenus…). This patch fixes it by fixing the calls to window.reallyContains(..) in order to take into account the stacking order to toplevel windows.

Also this patch fixes the issue described here: Small inconsistency in mouse position in popupmenu (the fact that the item “under mouse” when the menu is displayed is automatically opened if it is a submenu item).

And it also makes it possible to close / re-open a submenu by clicking on its parent item (convenient when submenus take a lot of space).