Search code examples
processingcontrol-p5

How to change color of each controlP5 dropdown list entry individually?


Using the following MWE:

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 change the font color of each list entry separately? I assume I'll have to use a for loop of some kind like the following

    for (int i =0; i < myarray.length; i++){
      dropdown.setColorActive(elementDetermine(myarray[i]));
    }

Where elementDetermine() takes an integer and returns a color. Unfortunately, when I run this loop in either the setup or the draw function, the dropdown list doesn't change without error message.


Solution

  • Unfortunately there's no easy readily available method to change the font color of each list entry separately.

    As you can see in the documentation the available methods access the main caption which applies to all items.

    In fact, the same Label component is re-used accross all list items and simply re-rendered once per item (changing current text/colours) as you can see in the source code

    If you really really need to change the font color you can, but that will be quite a few OOP hoops to jump through (since there is no array of Label instances per item):

    • you'd need to make a custom ControllerView< ScrollableList > to override it's display() method
    • for the custom display() to work the same as the original ScrollableList you need to pretty much replicate the super class
    • the ScrollableList super class has a bunch of private properties that can easily be accessed from the ScrollableListView class since it's part of ScrollableList, however in our case all those private / protected properties that need to be accessed in display() need to be accessible. To do this a ScrollableList subclass is implemenented (PopUpScrollableList) which mainly replicates it's superclass behaviours wherever those private/protected properties are used in display() and also makes them available.

    Finally the custom scrollable list can have a color[] which can store the custom colours so they can be accessed and rendered in the overriden display() method:

    import controlP5.*;
    // used by the custom scrollable list view
    import static controlP5.ControlP5.b;
    
    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 custom ScrollableList, by default it behaves like a DropdownList */
      PopUpScrollableList dropdown = addPopUpScrollableList("dropdown");
      
      dropdown.setPosition(100, 100)
         .setSize(200, 100)
         .setBarHeight(20)
         .setItemHeight(20)
         .addItems(l)
         // .setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST
         ;
      
      // a list of (random) colourr: a colour per item
      color[] textColors = new color[l.size()];
      for(int i = 0 ; i < textColors.length; i++){
        textColors[i] = color(255, random(255), random(255), random(255));
      }
      // set custom text colours: this is a bit hacky: normally you'd check if items.size() matches textColors.length, etc. 
      dropdown.textColors = textColors;
      
      // set a custom view for the list
      dropdown.setView(makeCustomScrollListView());
    }
    
    void draw() {
      background(240);
    }
    
    // simply adds our custom scrollable list
    PopUpScrollableList addPopUpScrollableList( final String theName ) {
      PopUpScrollableList myController = new PopUpScrollableList(cp5, theName);
      return myController;
    }
    
    ControllerView< ScrollableList > makeCustomScrollListView(){
      // make an custom scrollable list view on the fly and access the fileds it needs
      return new ControllerView< ScrollableList >() {
          
              // tweaked version of https://github.com/sojamo/controlp5/blob/1f7cb649865eb8657495b5cfeddd0dbe85d70cac/src/controlP5/ScrollableList.java#L391
              public void display( PGraphics g , ScrollableList c ) {
    
                // setHeight( -200 ); /* UP */
                PopUpScrollableList d = (PopUpScrollableList)c;
                g.noStroke( );
                
                if ( c.isBarVisible( ) ) {
                  boolean b = d.itemHover() == -1 && d.isInside() && !d.isDragged();
                  g.fill( b ? c.getColor( ).getForeground( ) : c.getColor( ).getBackground( ) );
                  g.rect( 0 , 0 , c.getWidth( ) , c.getBarHeight() );
                  g.pushMatrix( );
                  g.translate( c.getWidth( ) - 8 , c.getBarHeight() / 2 - 2 );
                  g.fill( c.getColor( ).getCaptionLabel( ) );
                  if ( c.isOpen( ) ) {
                    g.triangle( -3 , 0 , 3 , 0 , 0 , 3 );
                  } else {
                    g.triangle( -3 , 3 , 3 , 3 , 0 , 0 );
                  }
                  g.popMatrix( );
          
                  c.getCaptionLabel( ).draw( g , 4 , c.getBarHeight() / 2 );
          
                }
          
                if ( c.isOpen( ) ) {
                  int bar = ( c.isBarVisible( ) ? c.getBarHeight() : 0 );
                  int h = ( ( d.updateHeight( ) ) );
                  g.pushMatrix( );
                  // g.translate( 0 , - ( h + bar +
                  // c.itemSpacing ) ); /* UP */
                  g.fill( c.getBackgroundColor( ) );
                  g.rect( 0 , bar , c.getWidth( ) , h );
                  g.pushMatrix( );
                  g.translate( 0 , ( bar == 0 ? 0 : ( c.getBarHeight() + d.itemSpacing() ) ) );
                  /* draw visible items */
                  c.updateItemIndexOffset( );
                  int m0 = d.itemIndexOffset;
                  
                  List items = c.getItems();
                  
                  int m1 = items.size( ) > d.itemRange() ? ( d.itemIndexOffset + d.itemRange() ) : items.size( );
                  for ( int i = m0 ; i < m1 ; i++ ) {
                    Map< String , Object > item = (Map< String , Object >) items.get( i );
                    CColor itemColor = ( CColor ) item.get( "color" );
                    g.fill( ( b( item.get( "state" ) ) ) ? itemColor.getActive( ) : ( i == d.itemHover() ) ? ( c.isMousePressed() ? itemColor.getActive( ) : itemColor.getForeground( ) ) : itemColor.getBackground( ) );
                    
                    float boxY = d.itemHeight() - 1;
                    
                    g.rect( 0 , 0 , c.getWidth( ) , boxY );
                    Label label = c.getValueLabel( );
                    
                    // finally set custom text colour
                    if(d.textColors != null){ 
                      label.setColor(d.textColors[i]);
                    }
                    
                    label.set( item.get( "text" ).toString( ) ).draw( g , 4 , d.itemHeight() / 2 );
                    g.translate( 0 , d.itemHeight() );
                  }
                  g.popMatrix( );
          
                  if ( c.isInside() ) {
                    int m = items.size( ) - d.itemRange();
                    if ( m > 0 ) {
                      g.fill( c.getColor( ).getCaptionLabel( ) );
                      g.pushMatrix( );
                      int s = 4; /* spacing */
                      int s2 = s / 2;
                      g.translate( c.getWidth( ) - s , c.getBarHeight() );
                      int len = ( int ) PApplet.map( ( float ) Math.log( m * 10 ) , 0 , 10 , h , 0 );
                      int pos = ( int ) ( PApplet.map( d.itemIndexOffset , 0 , m , s2 , h - len - s2 ) );
                      g.rect( 0 , pos , s2 , len );
                      g.popMatrix( );
                    }
                  }
                  g.popMatrix( );
                }
          
              }
    
      };
    }
    
    
    // a custom ScrollableList subclass: mainly it needs to expose properties the custom view (via setView()) can't access in display()
    class PopUpScrollableList extends ScrollableList{
      
      private int itemIndexOffset = 0;
      private int _myType = DROPDOWN;
      protected boolean isOpen = true;
      
      public color[] textColors;
      
      PopUpScrollableList(ControlP5 cp5, String name){
        super(cp5, name);
        println("custom PopUpScrollableList named " + name + " constructed");
      }
     
      public boolean isOpen( ) {
        return isOpen;
      }
    
      public ScrollableList open( ) {
        return setOpen( true );
      }
    
      public ScrollableList close( ) {
        return setOpen( false );
      }
    
      public ScrollableList setOpen( boolean b ) {
        isOpen = b;
        return this;
      }
     
      public int itemHover(){
        return itemHover;
      }
      
      public int itemSpacing(){
        return 1;
      }
      
      public int itemRange(){
        return itemRange;
      }
      
      public int itemHeight(){
        return itemHeight;
      }
      
      public boolean isInside(){
        return isInside;
      }
      
      public boolean isDragged(){
        return isDragged;
      }
      
      public int barHeight(){
        return barHeight;
      }
      
      protected int updateHeight( ) {
        itemRange = ( PApplet.abs( getHeight( ) ) - ( isBarVisible( ) ? barHeight : 0 ) ) / itemHeight;
        return itemHeight * ( items.size( ) < itemRange ? items.size( ) : itemRange );
      }
      
      public List< Map< String , Object > > items(){
        return items;
      }
      
      @Override protected void onRelease( ) {
        if ( !isDragged ) {
          if ( getPointer( ).y( ) >= 0 && getPointer( ).y( ) <= barHeight ) {
            setOpen( !isOpen( ) );
          } else if ( isOpen ) {
    
            double n = Math.floor( ( getPointer( ).y( ) - barHeight ) / itemHeight );
    
            // n += itemRange; /* UP */
            int index = ( int ) n + itemIndexOffset;
            updateIndex( index );
          }
        }
      }
    
      private void updateIndex( int theIndex ) {
        if ( theIndex >= items.size( ) ) {
          return;
        }
    
        Map m = items.get( theIndex );
    
        switch ( _myType ) {
        case ( LIST ):
          super.setValue( theIndex );
          for ( Object o : items ) {
            ( ( Map ) o ).put( "state" , false );
          }
          m.put( "state" , !ControlP5.b( m.get( "state" ) ) );
          break;
        case ( DROPDOWN ):
          super.setValue( theIndex );
          setOpen( false );
          getCaptionLabel( ).setText( ( m.get( "text" ).toString( ) ) );
          break;
        case ( CHECKBOX ):
          m.put( "state" , !ControlP5.b( m.get( "state" ) ) );
          break;
        }
    
      }
    
      public ScrollableList setValue( float theValue ) {
        updateIndex( ( int ) ( theValue ) );
        return this;
      }
      
      @Override protected void onDrag( ) {
        scroll( getPointer( ).dy( ) );
      }
    
      @Override protected void onScroll( int theValue ) {
        scroll( theValue );
      }
    
      private void scroll( int theValue ) {
        if ( isOpen() ) {
          itemIndexOffset += theValue;
          itemIndexOffset = ( int ) ( Math.floor( Math.max( 0 , Math.min( itemIndexOffset , items.size( ) - itemRange ) ) ) );
          itemHover = -2;
        }
      }
      
      @Override protected void onLeave( ) {
        itemHover = -1;
      }
      
      @Override protected void onEnter( ) {
        updateHover( );
      }
    
      @Override protected void onMove( ) {
        updateHover( );
      }
    
      @Override protected void onEndDrag( ) {
        updateHover( );
      }
      
      private void updateHover( ) {
        if ( getPointer( ).y( ) > barHeight ) {
          double n = Math.floor( ( getPointer( ).y( ) - barHeight ) / itemHeight );
          itemHover = ( int ) ( itemIndexOffset + n );
        } else {
          itemHover = -1;
        }
      }
      
      public void updateItemIndexOffset( ) {
        int m1 = items.size( ) > itemRange ? ( itemIndexOffset + itemRange ) : items.size( );
        int n = ( m1 - items.size( ) );
        if ( n >= 0 ) {
          itemIndexOffset -= n;
        }
      }
      
      
    }
    

    controlp5 scrollable list custom font color per item