/*********************************************************************** * mt4j Copyright (c) 2008 - 2009 Christopher Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.components.visibleComponents.widgets; import java.util.ArrayList; import java.util.List; import org.mt4j.components.MTComponent; import org.mt4j.components.TransformSpace; import org.mt4j.components.interfaces.IMTController; import org.mt4j.components.visibleComponents.shapes.MTRectangle; import org.mt4j.input.gestureAction.DefaultDragAction; import org.mt4j.input.inputProcessors.IGestureEventListener; import org.mt4j.input.inputProcessors.MTGestureEvent; import org.mt4j.input.inputProcessors.componentProcessors.AbstractComponentProcessor; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragEvent; import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor; import org.mt4j.util.math.Vector3D; import processing.core.PApplet; /** * The Class MTList. A list component to add MTListCell objects to. * The layout will be done automatically. The list should be (created) vertically but * by rotating it, it can also be used horizontally. * * @author Christopher Ruff */ public class MTList extends MTClipRectangle { private float width; private float height; private float preferredCellWidth; private float preferredCellHeight; private MTListCellContainer listCellContainer; private float cellYPadding; //TODO dont paint listcells that are clipped entirely //TODO horizontal/vertical list //TODO padding, border between list cells etc //TODO deal with setWIdth/Height preferredWIdth etc changes //FIXME this is stupid, we got tapID and getID... //TODO getSelected, select() //TODO scrollbar in list and/or other indicator that we can scroll in a direction public MTList(float x, float y, float width, float height, PApplet applet) { this(x, y, width, height, 2, applet); } public MTList(float x, float y, float width, float height, float cellPaddingY, PApplet applet) { super(x, y, 0, width, height, applet); this.width = width; this.height = height; this.preferredCellWidth = this.width; this.preferredCellHeight = 50; this.cellYPadding = cellPaddingY; this.listCellContainer = new MTListCellContainer(x,y,1,1, applet); this.addChild(listCellContainer); //So we can drag the cell container when dragging on the list this.registerInputProcessor(new DragProcessor(applet)); this.removeAllGestureEventListeners(DragProcessor.class); this.addGestureListener(DragProcessor.class, new ListCellDragListener(listCellContainer)); } @Override protected void setDefaultGestureActions() { //No gestures } @Override public void addChild(MTComponent tangibleComp) { this.addChild(this.getChildCount(), tangibleComp); } @Override public void addChild(int i, MTComponent tangibleComp) { if (tangibleComp instanceof MTListCell) { MTListCell cell = (MTListCell) tangibleComp; this.addListElement(cell); }else{ super.addChild(i, tangibleComp); } } /** * Adds a list element. * * @param item the item */ public void addListElement(MTListCell item){ this.listCellContainer.addCell(listCellContainer.cells.size(), item); } /** * Adds a list element. * * @param index the index * @param item the item */ public void addListElement(int index, MTListCell item){ this.listCellContainer.addCell(index, item); } /** * Removes a list element. * * @param item the item */ public void removeListElement(MTListCell item){ this.listCellContainer.removeCell(item); } private Vector3D getListUpperLeftLocal(){ PositionAnchor savedAnchor = this.getAnchor(); this.setAnchor(PositionAnchor.UPPER_LEFT); Vector3D pos = this.getPosition(TransformSpace.LOCAL); this.setAnchor(savedAnchor); return pos; } private Vector3D getListLowerLeftLocal(){ PositionAnchor savedAnchor = this.getAnchor(); this.setAnchor(PositionAnchor.LOWER_LEFT); Vector3D pos = this.getPosition(TransformSpace.LOCAL); this.setAnchor(savedAnchor); return pos; } private Vector3D getContainerUpperLeftRelParent(){ PositionAnchor saved = listCellContainer.getAnchor(); listCellContainer.setAnchor(PositionAnchor.UPPER_LEFT); Vector3D returnPos = listCellContainer.getPosition(TransformSpace.RELATIVE_TO_PARENT); listCellContainer.setAnchor(saved); return returnPos; } private Vector3D getContainerLowerLeftRelParent(){ PositionAnchor saved = listCellContainer.getAnchor(); listCellContainer.setAnchor(PositionAnchor.LOWER_LEFT); Vector3D returnPos = listCellContainer.getPosition(TransformSpace.RELATIVE_TO_PARENT); listCellContainer.setAnchor(saved); return returnPos; } /** * The Class MTListCellContainer. Container for all the MTListCell's. * * @author Christopher Ruff */ private class MTListCellContainer extends MTRectangle{ private PApplet app; private List<MTListCell> cells; private List<MTListCell> selectedCells; //TODO! private boolean isDragging; public MTListCellContainer(float x, float y, float width, float height, PApplet applet) { super(x, y, width, height, applet); this.app = applet; this.setNoFill(true); //ENABLE LATER! this.setNoStroke(true); this.setPickable(false); this.cells = new ArrayList<MTListCell>(); this.selectedCells = new ArrayList<MTListCell>(); isDragging = false; } public void addCell(int index, MTListCell item){ if (cells.contains(item)){ return; } this.addChild(index, item); this.cells.add(index, item); this.updateLayout(); //Add drag listener which drags the cells parent (listcontainer) restriced to one axis if (!hasDragProcessor(item)){ item.registerInputProcessor(new DragProcessor(app)); } //Remove the default drag listener from the cell for safety IGestureEventListener[] l = item.getGestureListeners(); for (int j = 0; j < l.length; j++) { IGestureEventListener gestureEventListener = l[j]; if (gestureEventListener.getClass().equals(DefaultDragAction.class)){ item.removeGestureEventListener(DragProcessor.class, gestureEventListener); } } item.addGestureListener(DragProcessor.class, new ListCellDragListener(this)); //FIXME DEBUG REMOVE! /* //Add a tap processor and listener to the listcell to listen for tapps TapProcessor tap = new TapProcessor(app); tap.setMaxFingerUpDist(5); item.registerInputProcessor(tap); item.addGestureListener(TapProcessor.class, new IGestureEventListener() { public boolean processGestureEvent(MTGestureEvent ge) { TapEvent te = (TapEvent)ge; switch (te.getTapID()) { case TapEvent.BUTTON_DOWN: break; case TapEvent.BUTTON_UP: break; case TapEvent.BUTTON_CLICKED: System.out.println("Clicked ListCell Item: " + te.getTargetComponent()); break; default: break; } return false; } }); */ } private boolean hasDragProcessor(MTComponent comp){ AbstractComponentProcessor[] ps = comp.getInputProcessors(); for (int i = 0; i < ps.length; i++) { AbstractComponentProcessor p = ps[i]; if (p instanceof DragProcessor){ return true; } } return false; } public void removeCell(MTListCell item){ if (!this.containsDirectChild(item)){ return; } this.removeChild(item); this.cells.remove(item); this.updateLayout(); } public void updateLayout(){ //Extend/Shrink listCellContainer this.setWidthLocal(this.calcAllCellsWidth()); this.setHeightLocal(this.calcAllCellsHeight()); //Re-position list-container to upper left of MTList this.setAnchor(PositionAnchor.UPPER_LEFT); Vector3D listContainerUpperLeftLocal = getListUpperLeftLocal(); this.setPositionRelativeToParent(listContainerUpperLeftLocal); //Set the Position of the list cells in listCellContainer (cells are aligned to the left side of the container) float offset = 0; if (this.cells.size() > 0 && !this.cells.get(0).isNoStroke()){//FIXME TEST so that stroke isnt cut off TOP because of clipping offset = this.cells.get(0).getStrokeWeight(); } for (int i = 0; i < this.cells.size(); i++) { MTListCell cell = this.cells.get(i); cell.setAnchor(PositionAnchor.UPPER_LEFT); // Vector3D pos = new Vector3D(0, offset, 0); //FIXME TEST so that stroke isnt cut off left because of clipping Vector3D pos = new Vector3D(listContainerUpperLeftLocal.x + cell.getStrokeWeight(), listContainerUpperLeftLocal.y + offset, 0); cell.setPositionRelativeToParent(pos); offset += cellYPadding + cell.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); //TODO take strokeweight into account here,too } } /** * Adds up the heights of all cells. * Calc all cells height. * * @return the float */ private float calcAllCellsHeight() { float neededHeight = 0; for (int i = 0; i < cells.size(); i++) { MTListCell cell = cells.get(i); // neededHeight += preferredCellHeight + cellYPadding; if (i == cells.size()-1){ //because we dont need padding at the end of the list neededHeight += cell.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); }else{ neededHeight += cellYPadding + cell.getHeightXY(TransformSpace.RELATIVE_TO_PARENT); } } // neededHeight -= cellYPadding; //because we dont need padding at the end of the list return neededHeight; } private float calcAllCellsWidth() { float biggest = Float.MIN_VALUE; for (int i = 0; i < this.cells.size(); i++) { MTListCell cell = this.cells.get(i); float cellWidth = cell.getWidthXY(TransformSpace.RELATIVE_TO_PARENT); if (cellWidth >= biggest){ biggest = cellWidth; } } return biggest; // return preferredCellWidth; } public synchronized boolean isDragging() { return isDragging; } /** * Restrict dragging to only 1 listcell at a time. * @param isDragging */ public synchronized void setDragging(boolean isDragging) { this.isDragging = isDragging; } } /** * The Class ListCellDragListener. * Drag gestue listener which restricts dragging/scrolling of the cell container to its y-axis. * Also stops if the list end is reached. * * @author Christopher Ruff */ private class ListCellDragListener implements IGestureEventListener{ private MTListCellContainer theListCellContainer; private boolean canDrag; public ListCellDragListener(MTListCellContainer cont){ this.theListCellContainer = cont; this.canDrag = false; } public boolean processGestureEvent(MTGestureEvent ge) { DragEvent de = (DragEvent)ge; //If all cells fit into the list without scrolling dont do it. if (theListCellContainer.getHeightXY(TransformSpace.RELATIVE_TO_PARENT) <= getHeightXY(TransformSpace.LOCAL)){ if (canDrag && theListCellContainer.isDragging()){ theListCellContainer.setDragging(false); canDrag = false; } return false; } // Restrict dragging to only 1 listcell at a time if (!theListCellContainer.isDragging()){ // System.out.println("Dragging list with cursor: " + de.getDragCursor().getId()); theListCellContainer.setDragging(true); canDrag = true; } Vector3D dir = de.getTranslationVect(); //Transform the global direction vector into listCellContainer local coordiante space dir.transformDirectionVector(theListCellContainer.getGlobalInverseMatrix()); switch (de.getId()) { case MTGestureEvent.GESTURE_DETECTED: case MTGestureEvent.GESTURE_UPDATED: //Constrain the movement of the listcellcontainer to the boundaries of the List if (canDrag){ // if (dir.y > 0){ theListCellContainer.translate(new Vector3D(0, dir.y), TransformSpace.LOCAL); Vector3D listUpperLeftLocal = getListUpperLeftLocal(); if (getContainerUpperLeftRelParent().y > listUpperLeftLocal.y){ theListCellContainer.setAnchor(PositionAnchor.UPPER_LEFT); theListCellContainer.setPositionRelativeToParent(listUpperLeftLocal); } // }else if(dir.y < 0){ // theListCellContainer.translate(new Vector3D(0, dir.y), TransformSpace.LOCAL); Vector3D listLowLeftLocal = getListLowerLeftLocal(); if (getContainerLowerLeftRelParent().y < listLowLeftLocal.y){ theListCellContainer.setAnchor(PositionAnchor.LOWER_LEFT); theListCellContainer.setPositionRelativeToParent(listLowLeftLocal); } // } } break; case MTGestureEvent.GESTURE_ENDED: if (canDrag){ theListCellContainer.setDragging(false); Vector3D vel = de.getDragCursor().getVelocityVector(140); vel.scaleLocal(0.8f); vel = vel.getLimited(15); IMTController oldController = theListCellContainer.getController(); theListCellContainer.setController(new InertiaListController(theListCellContainer, vel, oldController)); } canDrag = false; break; default: break; } return false; } /** * The Class InertiaListController. * Controller to add an inertia scrolling after scrolling/dragging the list content. * * @author Christopher Ruff */ private class InertiaListController implements IMTController{ private MTComponent target; private Vector3D startVelocityVec; private float dampingValue = 0.95f; // private float dampingValue = 0.80f; private IMTController oldController; public InertiaListController(MTComponent target, Vector3D startVelocityVec, IMTController oldController) { super(); this.target = target; this.startVelocityVec = startVelocityVec; this.oldController = oldController; // System.out.println(startVelocityVec); //Animation inertiaAnim = new Animation("Inertia anim for " + target, new MultiPurposeInterpolator(startVelocityVec.length(), 0, 100, 0.0f, 0.5f, 1), target); } public void update(long timeDelta) { if (theListCellContainer.isDragging()){ startVelocityVec.setValues(Vector3D.ZERO_VECTOR); target.setController(oldController); return; } if (Math.abs(startVelocityVec.x) < 0.05f && Math.abs(startVelocityVec.y) < 0.05f){ startVelocityVec.setValues(Vector3D.ZERO_VECTOR); target.setController(oldController); return; } startVelocityVec.scaleLocal(dampingValue); Vector3D transVect = new Vector3D(startVelocityVec); transVect.transformDirectionVector(listCellContainer.getGlobalInverseMatrix()); theListCellContainer.translate(new Vector3D(0, transVect.y), TransformSpace.LOCAL); // System.out.println("Vel vect: " + transVect); //Constrain the movement of the listcellcontainer to the boundaries of the List Vector3D listUpperLeftLocal = getListUpperLeftLocal(); if (getContainerUpperLeftRelParent().y > listUpperLeftLocal.y){ theListCellContainer.setAnchor(PositionAnchor.UPPER_LEFT); theListCellContainer.setPositionRelativeToParent(listUpperLeftLocal); //Bounce off list end startVelocityVec.scaleLocal(-0.25f); } Vector3D listLowLeftLocal = getListLowerLeftLocal(); if (getContainerLowerLeftRelParent().y < listLowLeftLocal.y){ theListCellContainer.setAnchor(PositionAnchor.LOWER_LEFT); theListCellContainer.setPositionRelativeToParent(listLowLeftLocal); //Bounce off list end startVelocityVec.scaleLocal(-0.25f); } if (oldController != null){ oldController.update(timeDelta); } } } } public void scrollY(float amount){ listCellContainer.translate(new Vector3D(0, amount), TransformSpace.LOCAL); Vector3D listUpperLeftLocal = getListUpperLeftLocal(); if (getContainerUpperLeftRelParent().y > listUpperLeftLocal.y){ listCellContainer.setAnchor(PositionAnchor.UPPER_LEFT); listCellContainer.setPositionRelativeToParent(listUpperLeftLocal); } Vector3D listLowLeftLocal = getListLowerLeftLocal(); if (this.getContainerLowerLeftRelParent().y < listLowLeftLocal.y){ listCellContainer.setAnchor(PositionAnchor.LOWER_LEFT); listCellContainer.setPositionRelativeToParent(listLowLeftLocal); } } }