controlp5/src/controlP5/ScrollableList.java

484 lines
13 KiB
Java

package controlP5;
/**
* controlP5 is a processing gui library.
*
* 2006-2015 by Andreas Schlegel
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*
* @author Andreas Schlegel (http://www.sojamo.de)
* @modified ##date##
* @version ##version##
*
*/
import static controlP5.ControlP5.b;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.event.KeyEvent;
/**
* A ScrollableList is a list of vertically aligned items
* which can be scrolled if required.
*
* @example controllers/ControlP5scrollableList
*/
public class ScrollableList extends Controller< ScrollableList > implements ControlListener {
private int _myType = DROPDOWN;
protected int _myBackgroundColor = 0x00ffffff;
protected int itemHeight = 13;
protected int barHeight = 10;
private float scrollSensitivity = 1;
private boolean isOpen = true;
protected List< Map< String , Object > > items;
protected int itemRange = 5;
protected int itemHover = -1;
private int itemIndexOffset = 0;
private int itemSpacing = 1;
private int _myDirection = PApplet.DOWN;
private boolean isBarVisible = true;
static public final int LIST = ControlP5.LIST;
static public final int DROPDOWN = ControlP5.DROPDOWN;
static public final int CHECKBOX = ControlP5.CHECKBOX; /* TODO */
static public final int TREE = ControlP5.TREE; /* TODO */
public ScrollableList( ControlP5 theControlP5 , String theName ) {
this( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 0 , 99 , 199 );
theControlP5.register( theControlP5.papplet , theName , this );
}
protected ScrollableList( ControlP5 theControlP5 , ControllerGroup< ? > theGroup , String theName , int theX , int theY , int theW , int theH ) {
super( theControlP5 , theGroup , theName , theX , theY , theW , theH );
items = new ArrayList< Map< String , Object > >( );
updateHeight( );
getValueLabel( ).align( PApplet.LEFT , PApplet.CENTER );
}
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;
}
@Override public int getHeight( ) {
return isOpen ? super.getHeight( ) : barHeight;
}
public ScrollableList setType( int theType ) {
_myType = theType;
return this;
}
public void setDirection( int theDirection ) {
_myDirection = ( theDirection == PApplet.UP ) ? PApplet.UP : PApplet.DOWN;
}
@Override protected boolean inside( ) {
/* constrain the bounds of the controller to the
* dimensions of the cp5 area, required since
* PGraphics as render area has been introduced. */
float x0 = PApplet.max( 0 , x( position ) + x( _myParent.getAbsolutePosition( ) ) );
float x1 = PApplet.min( cp5.pgw , x( position ) + x( _myParent.getAbsolutePosition( ) ) + getWidth( ) );
float y0 = PApplet.max( 0 , y( position ) + y( _myParent.getAbsolutePosition( ) ) );
float y1 = PApplet.min( cp5.pgh , y( position ) + y( _myParent.getAbsolutePosition( ) ) + getHeight( ) );
if ( y1 < y0 ) {
float ty = y0;
y0 = y1;
y1 = ty;
}
return ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && _myControlWindow.mouseY > ( y1 < y0 ? y1 : y0 ) && _myControlWindow.mouseY < ( y0 < y1 ? y1 : y0 ) );
}
@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;
}
private void updateHover( ) {
if ( getPointer( ).y( ) > barHeight ) {
double n = Math.floor( ( getPointer( ).y( ) - barHeight ) / itemHeight );
itemHover = ( int ) ( itemIndexOffset + n );
} else {
itemHover = -1;
}
}
@Override protected void onEnter( ) {
updateHover( );
}
@Override protected void onMove( ) {
updateHover( );
}
@Override protected void onEndDrag( ) {
updateHover( );
}
private int updateHeight( ) {
itemRange = ( PApplet.abs( getHeight( ) ) - ( isBarVisible( ) ? barHeight : 0 ) ) / itemHeight;
return itemHeight * ( items.size( ) < itemRange ? items.size( ) : itemRange );
}
public ScrollableList setItemHeight( int theHeight ) {
itemHeight = theHeight;
updateHeight( );
return this;
}
public ScrollableList setBarHeight( int theHeight ) {
barHeight = theHeight;
updateHeight( );
return this;
}
public int getBarHeight( ) {
return barHeight;
}
public ScrollableList setScrollSensitivity( float theSensitivity ) {
scrollSensitivity = theSensitivity;
return this;
}
public ScrollableList setBarVisible( boolean b ) {
isBarVisible = b;
updateHeight( );
return this;
}
public boolean isBarVisible( ) {
return isBarVisible;
}
private Map< String , Object > getDefaultItemMap( String theName , Object theValue ) {
Map< String , Object > item = new HashMap< String , Object >( );
item.put( "name" , theName );
item.put( "text" , theName );
item.put( "value" , theValue );
item.put( "color" , getColor( ) );
item.put( "view" , new CDrawable( ) {
@Override public void draw( PGraphics theGraphics ) {
}
} );
item.put( "state" , false );
return item;
}
public ScrollableList addItem( String theName , Object theValue ) {
Map< String , Object > item = getDefaultItemMap( theName , theValue );
items.add( item );
return this;
}
public ScrollableList addItems( String[] theItems ) {
addItems( Arrays.asList( theItems ) );
return this;
}
public ScrollableList addItems( List< String > theItems ) {
for ( int i = 0 ; i < theItems.size( ) ; i++ ) {
addItem( theItems.get( i ).toString( ) , i );
}
return this;
}
public ScrollableList addItems( Map< String , Object > theItems ) {
for ( Map.Entry< String , Object > item : theItems.entrySet( ) ) {
addItem( item.getKey( ) , item.getValue( ) );
}
return this;
}
public ScrollableList setItems( String[] theItems ) {
setItems( Arrays.asList( theItems ) );
return this;
}
public ScrollableList setItems( List< String > theItems ) {
items.clear( );
return addItems( theItems );
}
public ScrollableList setItems( Map< String , Object > theItems ) {
items.clear( );
return addItems( theItems );
}
public ScrollableList removeItems( List< String > theItems ) {
for ( String s : theItems ) {
removeItem( s );
}
return this;
}
public ScrollableList removeItem( String theName ) {
if ( theName != null ) {
List l = new ArrayList( );
for ( Map m : items ) {
if ( theName.equals( m.get( "name" ) ) ) {
l.add( m );
}
}
items.removeAll( l );
}
return this;
}
public void updateItemIndexOffset( ) {
int m1 = items.size( ) > itemRange ? ( itemIndexOffset + itemRange ) : items.size( );
int n = ( m1 - items.size( ) );
if ( n >= 0 ) {
itemIndexOffset -= n;
}
}
public Map< String , Object > getItem( int theIndex ) {
return items.get( theIndex );
}
public Map< String , Object > getItem( String theName ) {
if ( theName != null ) {
for ( Map< String , Object > o : items ) {
if ( theName.equals( o.get( "name" ) ) ) {
return o;
}
}
}
return Collections.EMPTY_MAP;
}
public List getItems( ) {
return Collections.unmodifiableList( items );
}
public ScrollableList clear( ) {
for ( int i = items.size( ) - 1 ; i >= 0 ; i-- ) {
items.remove( i );
}
items.clear( );
itemIndexOffset = 0;
return this;
}
@Override public void controlEvent( ControlEvent theEvent ) {
// TODO Auto-generated method stub
}
public ScrollableList setBackgroundColor( int theColor ) {
_myBackgroundColor = theColor;
return this;
}
public int getBackgroundColor( ) {
return _myBackgroundColor;
}
@Override @ControlP5.Invisible public ScrollableList updateDisplayMode( int theMode ) {
_myDisplayMode = theMode;
switch ( theMode ) {
case ( DEFAULT ):
_myControllerView = new ScrollableListView( );
break;
case ( IMAGE ):
case ( SPRITE ):
case ( CUSTOM ):
default:
break;
}
return this;
}
static public class ScrollableListView implements ControllerView< ScrollableList > {
public void display( PGraphics g , ScrollableList c ) {
// setHeight( -200 ); /* UP */
g.noStroke( );
if ( c.isBarVisible( ) ) {
boolean b = c.itemHover == -1 && c.isInside && !c.isDragged;
g.fill( b ? c.getColor( ).getForeground( ) : c.getColor( ).getBackground( ) );
g.rect( 0 , 0 , c.getWidth( ) , c.barHeight );
g.pushMatrix( );
g.translate( c.getWidth( ) - 8 , c.barHeight / 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.barHeight / 2 );
}
if ( c.isOpen( ) ) {
int bar = ( c.isBarVisible( ) ? c.barHeight : 0 );
int h = ( ( c.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.barHeight + c.itemSpacing ) ) );
/* draw visible items */
c.updateItemIndexOffset( );
int m0 = c.itemIndexOffset;
int m1 = c.items.size( ) > c.itemRange ? ( c.itemIndexOffset + c.itemRange ) : c.items.size( );
for ( int i = m0 ; i < m1 ; i++ ) {
Map< String , Object > item = c.items.get( i );
CColor color = ( CColor ) item.get( "color" );
g.fill( ( b( item.get( "state" ) ) ) ? color.getActive( ) : ( i == c.itemHover ) ? ( c.isMousePressed ? color.getActive( ) : color.getForeground( ) ) : color.getBackground( ) );
g.rect( 0 , 0 , c.getWidth( ) , c.itemHeight - 1 );
c.getValueLabel( ).set( item.get( "text" ).toString( ) ).draw( g , 4 , c.itemHeight / 2 );
g.translate( 0 , c.itemHeight );
}
g.popMatrix( );
if ( c.isInside ) {
int m = c.items.size( ) - c.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.barHeight );
int len = ( int ) PApplet.map( ( float ) Math.log( m * 10 ) , 0 , 10 , h , 0 );
int pos = ( int ) ( PApplet.map( c.itemIndexOffset , 0 , m , s2 , h - len - s2 ) );
g.rect( 0 , pos , s2 , len );
g.popMatrix( );
}
}
g.popMatrix( );
}
}
}
public void keyEvent( KeyEvent theKeyEvent ) {
if ( isInside && theKeyEvent.getAction( ) == KeyEvent.PRESS ) {
switch ( theKeyEvent.getKeyCode( ) ) {
case ( ControlP5.UP ):
scroll( theKeyEvent.isAltDown( ) ? -itemIndexOffset : theKeyEvent.isShiftDown( ) ? -10 : -1 );
updateHover( );
break;
case ( ControlP5.DOWN ):
scroll( theKeyEvent.isAltDown( ) ? items.size( ) - itemRange : theKeyEvent.isShiftDown( ) ? 10 : 1 );
updateHover( );
break;
case ( ControlP5.LEFT ):
break;
case ( ControlP5.RIGHT ):
break;
case ( ControlP5.ENTER ):
onRelease( );
break;
}
}
}
/* TODO keycontrol: arrows, return dragging moving items
* sorting custom view custom event types */
}