• Hi everyone,

    I have a problem with the E.showMenu() function in the Bangle.js 2. Setup: An app shows its screen. Interaction is done via dragging over the screen or clicking into certain areas. All user input is handled via Bangle.on("drag",…). Short drags are interpreted as a "click".

    This way, when the user clicks into a certain area (a "button" so to say), a menu should open. I use E.showMenu(). Problem is: In 99% of all cases, the menu immediately detects a click on the first menu item. This happens before the menu is even shown. As the first menu item is "Continue", the app simply continues and it looks as the menu has never been presented at all.

    I have shrunken down my app to demonstrate the problem. I hope, the mere 123 lines aren't too long.

    // Test program for the event handling problem with the menu
    
    var gameValue = 3;
    var buttonsize = 30;
    
    // Evaluate drag and click events, massively reduce version which evaluates
    // each drag end as "click". That's sufficient to show the problem
    class Dragger {
      constructor(clickHandler) {
        this.clickHandler = clickHandler;
        this.enabled = true;
      }
      // Enable or disable the Dragger
      setEnabled(b) {
        this.enabled = b;
      }
      // Handle a raw drag event from the UI
      handleRawDrag(e) {
        if (!this.enabled)
          return;
        if (e.b === 0) { // Drag event ended: Evaluate full drag
          console.log("GGG - D.hRD - C, drag ended, assuming click");
          this.clickHandler(e); // take x and y from the drag end
        }
      }
      // Attach the drag evaluator to the UI
      attach() {
        Bangle.on("drag", e => this.handleRawDrag(e));
      }
    }
    
    // Representation of a field on the screen
    class Field {
      constructor(left, top) {
        this.left = left;
        this.top = top;
      }
      // Returns whether this field contains the given coordinate
      contains(x, y) {
        return (this.left < x && this.left + buttonsize > x &&
          this.top < y && this.top + buttonsize > y);
      }
      // Special field for the menu
      static forMenu() {
        return new Field(g.getWidth() - (buttonsize + 2), 26);
      }
    }
    
    // Representation of game area
    class Board {
      // Generates the actual playing field with all fields and buttons
      constructor() {
        this.menuField = Field.forMenu();
      }
      // Draws the complete playing field
      draw() {
        console.log("GGG - B.d - A");
        g.setFont("Vector", 20).setFontAlign(0, 0).setColor(0, 0, 0);
        g.drawString("Value: " + gameValue, g.getWidth() / 2, g.getHeight() / 2);
        g.drawRect(this.menuField.left, this.menuField.top, this.menuField.left + buttonsize, this.menuField.top + buttonsize);
      }
    }
    
    var board = new Board();
    var dragger;
    
    function continueGame(logmsg) {
      E.showMenu();
      board.draw();
      dragger.setEnabled(true);
      console.log(logmsg);
    }
    
    function initGame(bpl, logmsg) {
      gameValue = bpl;
      board = new Board();
      continueGame(logmsg);
    }
    
    function showMenu(withContinue) {
      var mainmenu = {
        "": {
          "title": "Menu"
        },
        "Continue": () => continueGame("GGG - menu detected Continue"),
        "Set 3": () => initGame(3, "GGG - menu detected Set 3"),
        "Set 4": () => initGame(4, "GGG - menu detected Set 4")
      };
      console.log("GGG - sM - B");
      dragger.setEnabled(false);
      g.clear(true);
      console.log("GGG - sM - C, mainmenu: " + mainmenu);
      E.showMenu(mainmenu);
      console.log("GGG - sM - D");
    }
    
    function handleclick(e) {
      console.log("GGG - handleclick - A, e: " + JSON.stringify(e));
      if (board.menuField.contains(e.x, e.y)) {
        console.log("GGG - handleclick - B, menu button pressed");
        showMenu();
        console.log("GGG - handleclick - C, menu shown");
      }
    }
    
    // Clear the screen once, at startup
    g.clear();
    
    // Clock mode allows short-press on button to exit
    Bangle.setUI("clock");
    // Load widgets
    Bangle.loadWidgets();
    Bangle.drawWidgets();
    
    // Draw the board initially
    board.draw();
    
    dragger = new Dragger(handleclick);
    dragger.attach();
    
    showMenu(); // works
    
    // end of file
    

    If you run this code, at first the menu is shown (explicitly in the main code, line 121). Then, the main part of the app starts. The square on the top right is the "menu button". If you click into it, the menu should open.

    ...only that it doesn't. In the emulator, you can only see that something is happening through the console log output. On the real hardware, the menu opens shortly and is immediately closed again. The console output shows in both cases that the first menu item, "Continue" is selected.

    In the emulator, you can trick the UI: If you perform a drag which ends outside the emulator window, then the next click on the menu button works. And on very rare occasions, the menu on the real hardware also does not close immediately but works flawlessly as expected. But you really need to be lucky to witness this behaviour.

    I do not know what is going on here. Do I make something wrong? Is it a bug in the menu system? Some interference with my way of interpreting input? Can anyone help me?

    Best regards,
    Dirk

  • Just a wild guess:
    1) Touching the screen first fires a drag event, and later a touch
    2) dragger opens the menu
    3) the menu sets up both drag and touch handlers: https://github.com/espruino/Espruino/blo­b/master/libs/js/banglejs/Bangle_setUI_Q­3.js#L46-L48
    4) firing the drag event finishes
    5) Now the touch event is fired and handled by the showMenu->setUI code (possibly this doesn't happen if you drag outside the window or drag/touch in just the "right" way)

    A possible solution might be changing showMenu(); to setTimeout(showMenu,0);, so all handlers get a chance to fire before the menu is set up.

  • Hi @rigrig ,

    thanks for explaining (or guessing…) what's going on! Can't have been too wrong, as setTimeout(showMenu,0) did not help, but setTimeout(showMenu,10) actually does! With 10 milisconds delay the events seem to be correctly assigned. Very cool!

    Best regards,
    Dirk

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

E.showMenu() gets spurious selections, problem listening to "drag" events?

Posted by Avatar for dirkhillbrecht @dirkhillbrecht

Actions