2

Using this MWE from the documentation:

import controlP5.*;
import java.util.*;


ControlP5 cp5;

void setup() {
  size(400, 400);
  cp5 = new ControlP5(this);
  List l = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
  /* add a ScrollableList, by default it behaves like a DropdownList */
  cp5.addScrollableList("dropdown")
     .setPosition(100, 100)
     .setSize(200, 100)
     .setBarHeight(20)
     .setItemHeight(20)
     .addItems(l)
     // .setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST
     ;
     
     
}

void draw() {
  background(240);
}

How can I get the dropdownlist to open up instead of down? I'm trying to place the list towards the lower edge of my screen. I could not find an option in the documentation.

Tea Tree
  • 882
  • 11
  • 26
  • 1
    Having a quick look at the [ScollableList source code](https://github.com/sojamo/controlp5/blob/master/src/controlP5/ScrollableList.java#L435) I see each item is added downwards. As far as I can tell it doesn't support configuring so it places the items upwards. If you really really need that as a scrollable list you should be able to subclass the `ScrollableList` and the `ScrollableListView` classes, override `ScrollableListView` `display()` method to translate in the opposite direction, etc. Another workaround I can think is using a toggle button with a list view above it... – George Profenza Mar 16 '21 at 19:46
  • ...when the button is toggled the list is displayed, otherwise it's hidden – George Profenza Mar 16 '21 at 19:47
  • I'm just starting out with processing so I think these workarounds are a bit beyond my current skills. Could you provide some starting point on how to subclass these? – Tea Tree Mar 16 '21 at 19:54
  • @TeaTree Are you familiar with inheritance? The method George Profenza speaks of is your best way to get what you want, but while it's not _that_ hard, it's not exactly entry-level either. [Here's an explanation with example](https://beginnersbook.com/2014/01/method-overriding-in-java-with-example/) to get you started. BUT there's an additional difficulty: `ControlP5` instantiate the class itself. This will make your job considerably more difficult. You'll need to be able to navigate and modify ControlP5 in several places. It's harder than it sounds, and you might want to switch tactics. – laancelot Mar 16 '21 at 22:21
  • I appreciate it. While I am somewhat familiar with inheritance, the trouble seems too big for what I'm trying to achieve. For the time being, I'll circumvent the issue altogether by placing the dropdown menu somewhere near the top. Of course, I'd still be interested in a solution. – Tea Tree Mar 16 '21 at 22:30
  • 2
    In all honesty, I think that the best course of action would be to download the project, amend it so there's an accessible property which lets the user choose if the dropdown goes upward or downward, then send the MR to the owner. He's still active so it may benefit everyone, and contributing to such a project looks good on a _curriculum vitae_. – laancelot Mar 16 '21 at 22:58

1 Answers1

3

Because of ControlP5's use of Java Generics inheritance is actually quite tricky in this case should ScrollableListView's display() method be overriden:

  • ScrollableList extends Controller< ScrollableList >
  • in java a class can subclass a single class, not multiple, hence the customized subclass like PopUpScrollableList would inherit Controller< ScrollableList > , not Controller< PopUpScrollableList >
  • there are a bunch of properties accessed in ScrollableListView's display() which belong to ScrollableList which are either private or protected

TLDR; @laancelot's suggestion to clone the library, modify the ScrollableListView's display() method directly and recompiling the library sounds like the easier option compared subclassing to a custom scroll list.

That being said, setting up an IDE and compiling a java library might not be the friendliest thing for a beginner, hence I can recommend a hacky workaround: simply shift the list's y position so it appears to be above instead of bellow:

import controlP5.*;
import java.util.*;


ControlP5 cp5;

ScrollableList list;
int listX = 100;
int listY = 100;
int listWidth = 200;
int listHeight = 100;
int barHeight = 20;

void setup() {
  size(400, 400);
  cp5 = new ControlP5(this);
  List l = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
  /* add a ScrollableList, by default it behaves like a DropdownList */
  list = cp5.addScrollableList("dropdown")
     .setPosition(listX, listY)
     .setSize(listWidth, listHeight)
     .setBarHeight(barHeight)
     .setItemHeight(20)
     .addItems(l)
     // .setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST
    ;
 
     
     
}

void draw() {
  // hack: shift the list up when it's open
  if(list.isOpen()){
    list.setPosition(listX, listY - listHeight + barHeight);
  }else{
    list.setPosition(listX, listY);
  }
  background(240);
}

controlp5 pop-up list

It's not perfect visually, may be a potentially akward user experience, but it's the simplest option for this custom behaviour using ControlP5.

Alternatively it's worth looking at customising a different UI library or writin a custom list.

For the sake of argument, here's an modified version of the list example from theGuido library:

/**
 *    A list
 *
 *
 *    Make sure to try your scroll wheel!
 */

import de.bezier.guido.*;

Listbox listbox;
SimpleButton button;
Object lastItemClicked;

void setup ()
{
    size(400, 400);
    
    // make the manager
    
    Interactive.make( this );
    
    // create a list box
    listbox = new Listbox( 20, 30, width-40, height-80 );
    for ( int i = 0, r = int(10+random(100)); i < r; i++ )
    {
        listbox.addItem( "Item " + i );
    }
    listbox.visible = false;
    // create button
    button = new SimpleButton("pop-up list", 20, 350, width-40, 24 );
    
}

void draw ()
{
    background( 20 );
    listbox.visible = button.on;
    if ( lastItemClicked != null )
    {
        fill( 255 );
        text( "Clicked " + lastItemClicked.toString(), 30, 20 );
    }
}

public void itemClicked ( int i, Object item )
{
    lastItemClicked = item;
}

public class Listbox
{
    float x, y, width, height;
    
    ArrayList items;
    int itemHeight = 20;
    int listStartAt = 0;
    int hoverItem = -1;
    
    float valueY = 0;
    boolean hasSlider = false;
    boolean visible = true;
    
    Listbox ( float xx, float yy, float ww, float hh ) 
    {
        x = xx; y = yy;
        valueY = y;
        
        width = ww; height = hh;
        
        // register it
        Interactive.add( this );
    }
    
    public void addItem ( String item )
    {
        if ( items == null ) items = new ArrayList();
        items.add( item );
        
        hasSlider = items.size() * itemHeight > height;
    }
    
    public void mouseMoved ( float mx, float my )
    {
        if(!visible){
          return;
        }
        if ( hasSlider && mx > width-20 ) return;
        
        hoverItem = listStartAt + int((my-y) / itemHeight);
    }
    
    public void mouseExited ( float mx, float my )
    {
        if(!visible){
          return;
        }
        hoverItem = -1;
    }
    
    // called from manager
    void mouseDragged ( float mx, float my )
    {
        if(!visible){
          return;
        }
        if ( !hasSlider ) return;
        if ( mx < x+width-20 ) return;
        
        valueY = my-10;
        valueY = constrain( valueY, y, y+height-20 );
        
        update();
    }
    
    // called from manager
    void mouseScrolled ( float step )
    {
        if(!visible){
          return;
        }
        valueY += step;
        valueY = constrain( valueY, y, y+height-20 );
        
        update();
    }
    
    void update ()
    {
        if(!visible){
          return;
        }
        float totalHeight = items.size() * itemHeight;
        float itemsInView = height / itemHeight;
        float listOffset = map( valueY, y, y+height-20, 0, totalHeight-height );
        
        listStartAt = int( listOffset / itemHeight );
    }
    
    public void mousePressed ( float mx, float my )
    {
        if(!visible){
          return;
        }
        if ( hasSlider && mx > width-20 ) return;
        
        int item = listStartAt + int( (my-y) / itemHeight);
        itemClicked( item, items.get(item) );
    }

    void draw ()
    {
        if(!visible){
          return;
        }
        noStroke();
        fill( 100 );
        rect( x,y,this.width,this.height );
        if ( items != null )
        {
            for ( int i = 0; i < int(height/itemHeight) && i < items.size(); i++ )
            {
                stroke( 80 );
                fill( (i+listStartAt) == hoverItem ? 200 : 120 );
                rect( x, y + (i*itemHeight), this.width, itemHeight );
                
                noStroke();
                fill( 0 );
                text( items.get(i+listStartAt).toString(), x+5, y+(i+1)*itemHeight-5 );
            }
        }
        
        if ( hasSlider )
        {
            stroke( 80 );
            fill( 100 );
            rect( x+width-20, y, 20, height );
            fill( 120 );
            rect( x+width-20, valueY, 20, 20 );
        }
    }
}

public class SimpleButton
{
    float x, y, width, height;
    boolean on;
    
    String label = "";
    
    SimpleButton ( float xx, float yy, float w, float h )
    {
        x = xx; y = yy; width = w; height = h;
        
        Interactive.add( this ); // register it with the manager
    }
    
    SimpleButton ( String label, float xx, float yy, float w, float h )
    {
      this(xx, yy, w, h);
      this.label = label;
    }
    
    // called by manager
    
    void mousePressed () 
    {
        on = !on;
    }

    void draw () 
    {
        if ( on ) fill( 200 );
        else fill( 100 );
        
        rect(x, y, width, height);
        
        if ( on ) fill( 100 );
        else fill( 200 );
        
        text(label, x + 10, y + this.height * 0.65);
    }
}

pop up list

The code is a lot more verbose in the sketch, but could be moved to a separate GUI tab. Hopefully because it is simpler it can be manipulated easier than ControlP5 for custom behaviours.

George Profenza
  • 50,687
  • 19
  • 144
  • 218