Design pattern for complex mouse operations

edited August 2014 in How To...

Hi I'm developing a little software to manipulate 2d shapes. I'm doing a lot of mouse commands: group/ungroup shapes, select single shape/group, rotate/translate single shape/group or all the canvas, etc etc Right now I'm doing it with weird different keys and mouse click combinations: click on shape while pressing the key 's' is select shape, while pressing 'g' is group, etc..

I'd like to do it with more structured mouse commands: one click, double click, one right click -> contextual menu, fright click and drag, etc etc etc

I've tried to see awt and there is the MouseAdapter but it's for awt objects and my shape classes (I'm using toxiclibs to handle 2d geometry) are not suitable for that. Or anyway not without a lot of works for adapting. And I'd like to stay more p5 as possible, using controlP5 as guy library.

Is there any kind of strategy/design pattern that you guys in the forum have used for such a structured thing?

Tagged:

Answers

  • Perhaps this thread can give some inspiration with regard to the right-click menu.

  • Hm, what about a menu in one corner where you can select group and rotate etc. ?

  • Yes could also be but I'd like the idea to make it more "softwareish" like pan tilt zoom in rhino, when modelling on the 3d perspective view. or click and select, right click, etc etc

    Do you have it in mind? A key and a mouse action makes an operation in the world (the 3d perspective) AND in the gui visualization (change the mouse cursor for example)

    I think that in the pure awt world this is done with a class that act as a mouse controller.. i'm looking for a similar pattern with no awt objects and events firing but with native processing

  • edited August 2014

    Menu like here,,,,

    import javax.swing.JOptionPane.* ;
    
    final String title = "Note Manager";
    final String statusBarText= "Please use right mouse on empty space " + 
    "or on a note. Use left mouse button on main menu.";
    
    ArrayList<DraggableB> notes = new ArrayList();
    int selected; // while dragging this is the index
    String strInput;
    
    int xPosForNewNote = 40;
    int yPosForNewNote = 70;
    
    // this is a self made local menu without lib 
    // 
    ArrayList<LocalMenu> menus = new ArrayList();
    
    boolean menuOpen = false; 
    int  menuOpenNumber = -1; // which menu is open 
    
    // ----------------------------------------------------
    // main funcs 
    
    void setup() {
    
      size(640, 360);
      rectMode(CENTER);
    
      // make menu 1: local menu on existing note
      ArrayList<LineInTheLocalMenu> lines = new ArrayList();  
      lines.add( new LineInTheLocalMenu ( "Size  ", "Size ", 0 ));
      lines.add( new LineInTheLocalMenu ( "Edit Text ", "Edit the text of the note", 1 ));
      lines.add( new LineInTheLocalMenu ( "Delete", "Delete the note", 2 ));
      lines.add( new LineInTheLocalMenu ( "Cancel ", "Close the menu", 3 ));
      LocalMenu test = new LocalMenu(333.0, 133.0, "Menu for a Note", "Menu to change a Note", lines);
      menus.add( test );
      test = null;
    
      // make menu 2: local menu on empty space 
      lines = new ArrayList();  
      lines.add( new LineInTheLocalMenu ( "New Note ", "This creates a new note.", 0));
      lines.add( new LineInTheLocalMenu ( "Cancel ", "Close the menu", 1 ));
      LocalMenu test2 = new LocalMenu(93, 233, "Menu for a new note ", "create a new note", lines); 
      menus.add(test2);
      test2 = null;
    
      // make menu 3: main menu at menu symbol (=)
      lines = new ArrayList();  
      lines.add( new LineInTheLocalMenu ( "New Sheet  ", "This creates a new sheet (deletes all).", 0));
      lines.add( new LineInTheLocalMenu ( "New Note  ", "This creates a new note.", 1));
      lines.add( new LineInTheLocalMenu ( "Make stack  ", "place all notes on a stack.", 2));
      lines.add( new LineInTheLocalMenu ( "Spread notes  ", "Spread all notes ", 3));
      lines.add( new LineInTheLocalMenu ( "Cancel", "Close the menu", 4));
      LocalMenu test3 = new LocalMenu(30, 30, "Main Menu", "The main menu", lines); 
      menus.add(test3);
      test3 = null;
      //
    } // func 
    
    void draw() {
      background(255);
    
      fill(0);
      textAlign(CENTER);
      text(title, width/2, 18);
    
      textAlign(LEFT);
      menuSign(30, 5);
      showStatusBar(statusBarText);
    
      // show notes 
      for (int i = 0; i < notes.size(); i++) {
        DraggableB currNote = notes.get(i);
        currNote.update();
        currNote.display();
      } // for
    
      // show menu when necessary 
      if (menuOpen) {
        LocalMenu myMenu = menus.get(menuOpenNumber);
        myMenu.display();
      } // if
      //
    } // func 
    
    // --------------------------------------------------------
    // Inputs mouse 
    
    void mousePressed() {
      //
      if (menuOpen) {
        mousePressedForMenuOpen ();
      }
      else {
        mousePressedForMenuNotOpen ();
      }
      //
    } // func 
    
    void mouseDragged() {
      if (selected!=-1 && selected < notes.size()) {
        if (notes.get(selected).lock) {
          notes.get(selected).posX = mouseX - notes.get(selected).xOffset; 
          notes.get(selected).posY = mouseY - notes.get(selected).yOffset;
        }
      }
    } // func  
    
    void mouseReleased() {
      if (selected!=-1 && selected < notes.size()) {
        notes.get(selected).lock = false;
      }
    } // func 
    
    // ---------------------------------------------------
    
    void mousePressedForMenuOpen () {
      // menu is open 
      if (mouseButton==RIGHT) {
        // right-click: open another (or same) local menu
        mousePressedForMenuNotOpen ();
      } // if 
      else if (mouseButton==LEFT) {
        // left-click:
        LocalMenu myMenu = menus.get(menuOpenNumber);
        int command = myMenu.overWhichLine();
        if (command!=-1) {
          menuOpen=false;
          doCommand(command);
        }
        else
        {
          // no command identified 
          // close the menu 
          menuOpen=false;
        }
      } // else
    } // func 
    //
    //
    void mousePressedForMenuNotOpen () {
      // menu is not open
      // open a menu if necessary 
      if (mouseButton==RIGHT) {
        menuOpenNumber = -1;
    
        for (int i = 0; i < notes.size(); i++) {
          DraggableB currNote = notes.get(i);
          if (currNote.over) {
            menuOpenNumber=0;
            selected = i;
          }
        } // for
    
        // did he find no item?
        if (menuOpenNumber == -1) {
          menuOpenNumber=1;
          selected = -1;
        }
    
        //  
        // did he find an item?
        if (menuOpenNumber > -1) { 
          menuOpen=true;
          LocalMenu myMapPoint = menus.get(menuOpenNumber);
          myMapPoint.pos.x = mouseX;
          myMapPoint.pos.y = mouseY;
        } // if
        else
        {
          menuOpen=false;
        }
      } // if Button RIGHT
      else
      {
        // LEFT mouse 
        menuOpen=false;
    
        if (mouseX>26&&mouseX<60&&
          mouseY>0&&mouseY<30) {
          menuOpen=true; 
          menuOpenNumber=2;
          selected = -1;
        }// if 
    
        else {
    
          // note: start dragging 
          for (int i = 0; i < notes.size(); i++) {
            DraggableB currNote = notes.get(i);
            if (currNote.over) {
              currNote.lock = true; 
              currNote.xOffset = mouseX - currNote.posX; 
              currNote.yOffset = mouseY - currNote.posY;
              selected=i; // store index
              return;
            } 
            else {
              currNote.lock = false;
            }
          } // for
        } // else
      } // else LEFT mouse 
      //
    } // func 
    
    // -----------------------------------------------
    // Inputs keyboard 
    
    void keyPressed () {
      if (menuOpen) {
        // kill key (in case its escape key)
        key=0;
      }
      else
      {
        DraggableB currNote;
        switch (key) {
        case 'n':
          // make a new note 
          newNote(); 
          break;
        case 'e':
          // edit a note
          for (int i = 0; i < notes.size(); i++) {
            currNote = notes.get(i);
            if (currNote.over) {
              strInput = javax.swing.JOptionPane.showInputDialog("Please enter new text", currNote.text);
              currNote.text=strInput;
              return;
            }
          }
          break; 
    
        case '+':
          // size +
          selected=-1;
          for (int i = 0; i < notes.size(); i++) {
            currNote = notes.get(i);
            if (currNote.over) 
              selected=i;
          }
          if (selected!=-1 && selected < notes.size()) {
            notes.get(selected).size++;
          }
          break;
    
        case '-':
          // size - 
          selected=-1;
          for (int i = 0; i < notes.size(); i++) {
            currNote = notes.get(i);
            if (currNote.over) 
              selected=i;
          }
          if (selected!=-1 && selected < notes.size()) {
            if (notes.get(selected).size>8)
              notes.get(selected).size--;
          }
          break;
    
        case DELETE:
          // delete note 
          for (int i = 0; i < notes.size(); i++) {
            currNote = notes.get(i);
            if (currNote.over) {
              notes.remove(i); 
              return;
            }
          }
          break; 
        default:
          // Do nothing 
          break;
        } // switch
      } // else  
      //
    } // func 
    
    // ------------------------------------------------------
    // menu management 
    
    void doCommand(int command) {
    
      // handles the command from a menu
    
      final int menu0 = 0;
      final int menu1 = 1;
      final int menu2 = 2;
      //
      // for the different menus the lines (commands)
      // are different: 
      switch (menuOpenNumber) {
      case menu0:
        handleMenu0(command);
        break;
      case menu1:
        handleMenu1(command);
        break;
      case menu2:
        handleMenu2(command);
        break;
      default:
        println ("unknown menu");
        break;
      } // switch
    } // func 
    
    //
    void handleMenu0 (int command) {
      // local menu on a note
      switch (command) {
      case 0:
        println (" 0 : 0 " );
        notes.get(selected).size+=20; 
        break;
      case 1:
        println (" 0 : 1 " );
        strInput = javax.swing.JOptionPane.showInputDialog("Please enter new text", notes.get(selected).text);
        if (strInput!=null) { 
          notes.get(selected).text=strInput;
        }
        break;
      case 2:
        println (" 0 : 2 " );
        notes.remove(selected); 
        break;
      case 3:
        println (" 0 : 3 " );
        menuOpen = false;
        menuOpenNumber = -1; 
        selected = -1; 
        break;
      default:
        break;
      } // inner switch
    }
    
    //
    void handleMenu1 (int command) {
      // local menu on empty space
      switch (command) {
    
      case 0:
        // add a new note 
        menuOpen=false; 
        strInput = javax.swing.JOptionPane.showInputDialog("Please enter new text");
        if (strInput!=null) { 
          // note at mouse pos 
          DraggableB currNote = new DraggableB(mouseX, mouseY, 60, strInput);
          notes.add( currNote );
        } // if 
        break;
    
      case 1:
        println (" 1 : 1 " );
        menuOpen = false;
        menuOpenNumber = -1; 
        selected = -1; 
        break;
    
      case 2:
        println (" 1 : 2 " );
        break;
      case 3:
        println (" 1 : 3 " );
        break;
      case 4:
        println (" 1 : 4 " );
        break;
      default:
        break;
      } // switch
    }
    
    //
    void handleMenu2 (int command) {
      // main menu (=)
      switch (command) {
    
      case 0:
        // new sheet : delete all 
        menuOpen=false; 
        notes.clear();  // reset 
        selected = -1; 
        xPosForNewNote = 40; 
        yPosForNewNote = 70; 
        break;
    
      case 1:
        // new note
        newNote();  
        menuOpen=false;
        break;
    
      case 2:
        println (" 2 : 2 " );
        for (int i = 0; i < notes.size(); i++) {
          DraggableB currNote = notes.get(i);
          currNote.posX = int(random ( 40, 60 ));
          currNote.posY = int(random ( 60, 80 ));
        } // for  
        menuOpen=false;
        break;
    
      case 3:
        int posX=50; 
        int posY=70; 
        for (int i = 0; i < notes.size(); i++) {
          DraggableB currNote = notes.get(i);
          currNote.posX = posX;
          currNote.posY = posY;
          posX+=currNote.size+12;
          if (posX > width - 70) {
            posX=50;
            posY+=70;
          }
        } // for  
        menuOpen=false;
        break;
    
      case 4:
        //cancel
        menuOpen=false;
        break;
    
      default:
        break;
      } // inner switch
    }
    // ----------------------------------------------------------------
    // commands from the menus 
    
    void newNote() {
      String strInput = javax.swing.JOptionPane.showInputDialog("Please enter new text");
      if (strInput!=null) { 
        DraggableB currNote = new DraggableB(xPosForNewNote, yPosForNewNote, 60, strInput);
        notes.add( currNote );
        xPosForNewNote += 80; 
        if ( xPosForNewNote > width-40) {
          xPosForNewNote = 40;
          yPosForNewNote += 80;
        } // if
      } // if
    } // func 
    
    // ----------------------------------------------------------------
    // minor tools 
    
    void showStatusBar ( String myText ) {
      rectMode(CORNER);
      noStroke();
      fill(111);
      rect(0, height-30, width, 30);
      fill(255);
      text (myText, 20, height-8);
      rectMode(CENTER);
    }
    
    void menuSign(int x, int y) {
      rectMode(CORNER);
      noStroke();
      fill(100);
      int w = 20;
      int h = 4;
      rect( x, y, w, h, 8 );
      rect( x, y+h+2, w, h, 8 );
      rect( x, y+(h+2)*2, w, h, 8 );
      rectMode(CENTER);
    }
    
    // =======================================
    // classes 
    // 
    
    class DraggableB {
    
      int posX;
      int posY;
      int xOffset;
      int yOffset;
    
      int size;
      int halfSize;
    
      boolean over;
      boolean lock;
    
      String text=""; 
    
      DraggableB(int tempX, int tempY, 
      int tempSize, 
      String tempText) {
        posX = tempX;
        posY = tempY;
        size = tempSize;
        halfSize = size/2;
        text = tempText;
      } // constr
    
      void update() {
        // update
        fill(255, 255, 0); // yellow 
        if (!menuOpen) {
          if (mouseX >= posX - halfSize && mouseX <= posX + halfSize && 
            mouseY >= posY - halfSize && mouseY <= posY + halfSize) {
            over = true;  
            if (!lock) {
              stroke(0); 
              fill(255, 0, 0); // red
              //size = 30;
            } 
            else {
              fill(0, 0, 255); // blue
            } // else
          } // if  
          else {
            stroke(0);
            fill(255, 255, 0); // yellow 
            // size = size - 10; // ???
            over = false;
          } //else
        } // if
      } // method
    
      void display() {
        //noStroke();
        stroke(90);
        rect(posX, posY, size, size, 
        0, 0, 23, 0);
        fill(0); 
        if (text!=null)
          text(text, posX+2, posY+2, size, size);
      } // method
      //
    } // class 
    // 
    // =====================================
    
    class LocalMenu {
      //
      PVector pos = new PVector();
      PVector size = new PVector();
      String name;
      String textMouseOver;
      ArrayList<LineInTheLocalMenu> lines = new ArrayList();  
      //
      // constr
      LocalMenu ( 
      float x_, float y_, 
      String name_, 
      String textMouseOver_, 
      ArrayList<LineInTheLocalMenu> lines_ ) {
        pos.x=x_;
        pos.y=y_;
        name=name_;
        textMouseOver=textMouseOver_;
        lines=lines_;
        //
        // get the size of this menu
        float myWidth= textWidth (name) ;
        int i = 0; 
        for (LineInTheLocalMenu oneLine : lines ) {
          if (myWidth<textWidth (oneLine.content)) 
            myWidth = textWidth (oneLine.content) ;
          i++;
        }
        size.x = myWidth+15;
        size.y = 15*i+15+15+4; // to do
        //
      } // constr
      //
      void display() {
        // 1st: rect 
        rectMode(CORNER);
        noStroke();
        fill (121);
        rect(pos.x, pos.y, size.x, size.y );
        // 2nd: headline 
        fill(0);
        text(name, pos.x+15, pos.y+15);
        // 3rd: normal menu lines 
        int i=0;
        boolean doneStatusBar = false;
        for (LineInTheLocalMenu oneLine : lines ) {
          fill(245);
          text(oneLine.content, pos.x+15, pos.y +15*i+15+15+4); // to do 
          i++;
          oneLine.pos.x = pos.x+1;
          oneLine.pos.y = pos.y+15*i-29+15+15+4;    // to do
          if (oneLine.mouseOver() && mouseX < pos.x+size.x  && !doneStatusBar )
          {
            oneLine.over = true; 
            showStatusBar ( oneLine.textMouseOver );
            doneStatusBar=true;
          }
          else
          {
            oneLine.over = false;
          }
        }
        if (!doneStatusBar) {
          if (mouseOver() ) {
            showStatusBar (  textMouseOver  );
            doneStatusBar=true;
          }
        }
        rectMode(CENTER);
      } // method
    
      boolean mouseOver() {
        if (mouseX>pos.x && 
          mouseY>pos.y && 
          mouseX<pos.x+size.x &&
          mouseY<pos.y+size.y ) {
          return true;
        }
        return false;
      } // method
    
      //
    
      int overWhichLine() {
        int i=0;
        for (LineInTheLocalMenu oneLine : lines ) {
          fill(245);
          // text(oneLine.content, pos.x+15, pos.y +15*i+15+15+4); // to do 
          i++;
          oneLine.pos.x = pos.x+1;
          oneLine.pos.y = pos.y+15*i-29+15+15+4;    // to do
          if (oneLine.mouseOver() && mouseX < pos.x+size.x )
          {
            oneLine.over = true; 
            return oneLine.commandID;
          }
        } // for
        return -1;
      } // func 
    
      //
    } // class
    
    // ===============================================================
    
    class LineInTheLocalMenu {
    
      PVector pos = new PVector();
      String content;
      String textMouseOver;
      boolean over = false; 
      int commandID=0;
      // 
      // constr
      LineInTheLocalMenu ( String content_, String textMouseOver_, int commandID_ ) {
        content=content_;
        textMouseOver=textMouseOver_;
        commandID = commandID_;
      }// constr
      //
      boolean mouseOver() {
        if (mouseX>pos.x && 
          mouseY>pos.y && 
          mouseY<pos.y + 19 ) {
          return true;
        }
        return false;
      } // method
      //
    } // class 
    
    // =============================================
    
Sign In or Register to comment.