package org.mt4jx.components.visibleComponents.widgets.menus;
import java.util.ArrayList;
import java.util.List;
import org.mt4j.AbstractMTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.clipping.Clip;
import org.mt4j.components.css.style.CSSFont;
import org.mt4j.components.css.style.CSSStyle;
import org.mt4j.components.css.util.CSSFontManager;
import org.mt4j.components.css.util.CSSHelper;
import org.mt4j.components.css.util.CSSStylableComponent;
import org.mt4j.components.css.util.CSSKeywords.CSSFontWeight;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.shapes.MTRectangle.PositionAnchor;
import org.mt4j.components.visibleComponents.widgets.MTOverlayContainer;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.zoomProcessor.ZoomProcessor;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.font.IFont;
import org.mt4j.util.math.Vector3D;
import org.mt4jx.components.visibleComponents.widgets.menus.MenuItem;
import processing.core.PConstants;
import processing.core.PImage;
/**
* The Class MTHUD.
*/
public class MTHUD extends MTOverlayContainer implements CSSStylableComponent {
/** The Constant LEFT. */
public final static short LEFT = 1;
/** The Constant RIGHT. */
public final static short RIGHT = 2;
/** The Constant TOP. */
public final static short TOP = 3;
/** The Constant BOTTOM. */
public final static short BOTTOM = 4;
/** The app. */
private AbstractMTApplication app;
/** The menu contents. */
private List<MTRectangle> menuContents = new ArrayList<MTRectangle>();
/** The menu items. */
private List<MenuItem> menuItems = new ArrayList<MenuItem>();
/** The size. */
private float size;
/** The offset. */
private float offset;
/** The position. */
private short position;
/** The css helper. */
private CSSHelper cssHelper;
/** The mt app. */
private AbstractMTApplication mtApp;
/** The css styled. */
private boolean cssStyled = false;
/** The css force disabled. */
private boolean cssForceDisabled = false;
/**
* Instantiates a new MTHUD.
*
* @param applet
* the applet
* @param menuItems
* the menu items
*/
public MTHUD(AbstractMTApplication applet, List<MenuItem> menuItems) {
this(applet, menuItems, 64, 8, LEFT);
}
/**
* Instantiates a new MTHUD.
*
* @param applet
* the applet
* @param menuItems
* the menu items
* @param size
* the size
*/
public MTHUD(AbstractMTApplication applet, List<MenuItem> menuItems, float size) {
this(applet, menuItems, size, (float) Math.sqrt(size), LEFT);
}
/**
* Instantiates a new MTHUD.
*
* @param applet
* the applet
* @param menuItems
* the menu items
* @param size
* the size
* @param offset
* the offset (distance between items)
* @param position
* the position (MTHUD.LEFT, MTHUD.RIGHT, MTHUD.TOP,
* MTHUD.BOTTOM)
*/
public MTHUD(AbstractMTApplication applet, List<MenuItem> menuItems, float size, float offset, short position) {
super(applet);
this.app = applet;
this.menuItems = menuItems;
this.size = size;
this.offset = offset;
this.position = position;
this.setCssForceDisable(true);
this.cssHelper = new CSSHelper(this, applet);
createMenuItems();
}
/**
* Instantiates a new MTHUD.
*
* @param applet
* the applet
* @param menuItems
* the menu items
* @param size
* the size
* @param position
* the position (MTHUD.LEFT, MTHUD.RIGHT, MTHUD.TOP,
* MTHUD.BOTTOM)
*/
public MTHUD(AbstractMTApplication applet, List<MenuItem> menuItems, float size, short position) {
this(applet, menuItems, size, (float) Math.sqrt(size), position);
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#applyStyleSheet()
*/
public void applyStyleSheet() {
if (cssStyled && mtApp != null && cssHelper != null) {
cssHelper.applyStyleSheet(this);
}
}
/**
* Creates the menu items from the complete list of MenuItems
*/
public void createMenuItems() {
this.createMenuItems(menuItems);
}
/**
* Creates the menu items (from the list of items provided)
*
* @param items
* the items
*/
public void createMenuItems(List<MenuItem> items) {
for (MTComponent c : this.getChildren()) {
c.destroy();
}
this.removeAllChildren();
menuContents.clear();
for (MenuItem s : items) {
if (s != null && s.getType() == MenuItem.TEXT) {
// Create a new menu cell
MTRectangle container = new MTRectangle(app, 0, 0, size, size);
this.addChild(container);
// Add MTTextArea Children to take single lines of the Menu Text
for (String t : s.getMenuText().split("\n")) {
MTTextArea menuItem = new MTTextArea(app);
menuItem.setText(t);
menuItem.setCssForceDisable(true);
menuItem.setFillColor(new MTColor(0, 0, 0, 0));
menuItem.setStrokeColor(new MTColor(0, 0, 0, 0));
menuItem.setPickable(false);
container.addChild(menuItem);
}
container.setChildClip(new Clip(container));
container.setGestureAllowance(TapProcessor.class, true);
container.registerInputProcessor(new TapProcessor(app));
container.addGestureListener(TapProcessor.class,
s.getGestureListener());
container.setCssForceDisable(true);
container.setGestureAllowance(DragProcessor.class, false);
container.setGestureAllowance(RotateProcessor.class, false);
container.setGestureAllowance(ZoomProcessor.class, false);
container.setGestureAllowance(ScaleProcessor.class, false);
menuContents.add(container);
} else if (s != null && s.getType() == MenuItem.PICTURE) {
if (s.getMenuImage() != null) {
PImage texture = null;
// If Image doesn't fit, make it fit!
if (s.getMenuImage().width != (int) size
|| s.getMenuImage().height != (int) size) {
texture = cropImage(s.getMenuImage(), (int) size, true);
} else {
texture = s.getMenuImage();
}
// Create a new menu cell
MTRectangle container = new MTRectangle(app, 0, 0, size,
size);
this.addChild(container);
// Set the background texture
container.setTexture(texture);
container.setChildClip(new Clip(container));
container.setGestureAllowance(TapProcessor.class, true);
container.registerInputProcessor(new TapProcessor(app));
container.addGestureListener(TapProcessor.class,
s.getGestureListener());
container.setCssForceDisable(true);
container.setGestureAllowance(DragProcessor.class, false);
container.setGestureAllowance(RotateProcessor.class, false);
container.setGestureAllowance(ZoomProcessor.class, false);
container.setGestureAllowance(ScaleProcessor.class, false);
menuContents.add(container);
}
}
}
this.styleChildren();
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#disableCSS()
*/
public void disableCSS() {
cssStyled = false;
}
/**
* Display all items in the MenuList
*/
public void displayAll() {
this.createMenuItems();
}
/**
* Display a subset of the menu
*
* @param items
* the items as int[] (items 1,2,3,...n)
*/
public void displaySubset(int[] items) {
this.createMenuItems(this.getRelevantItems(items));
}
/**
* Display a subset of the menu
*
* @param items
* the items as List<MenuItem> to display
*/
public void displaySubset(List<MenuItem> items) {
this.createMenuItems(this.getRelevantItems(items));
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#enableCSS()
*/
public void enableCSS() {
if (mtApp != null && cssHelper != null) {
cssStyled = true;
}
applyStyleSheet();
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#getCssHelper()
*/
public CSSHelper getCssHelper() {
return this.cssHelper;
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#isCssForceDisabled()
*/
public boolean isCssForceDisabled() {
return cssForceDisabled;
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#isCSSStyled()
*/
public boolean isCSSStyled() {
return cssStyled;
}
/*
* (non-Javadoc)
*
* @see org.mt4j.css.util.CSSStylableComponent#setCssForceDisable(boolean)
*/
public void setCssForceDisable(boolean cssForceDisabled) {
this.cssForceDisabled = cssForceDisabled;
}
/**
* Sets the menu items and reinstantiates the menu.
*
* @param menuItems
* the new menu items
*/
public void setMenuItems(List<MenuItem> menuItems) {
this.menuItems = menuItems;
createMenuItems();
}
/**
* Sets the size.
*
* @param size
* the new size
*/
public void setSize(float size) {
this.size = size;
this.createMenuItems();
}
/**
* Calculates the total height of a number of MTTextAreas.
*
* @param components
* the components
* @return the height
*/
private float calcTotalHeight(MTComponent[] components) {
float height = 0;
for (MTComponent c : components) {
if (c instanceof MTTextArea)
height += ((MTTextArea) c).getHeightXY(TransformSpace.LOCAL);
}
return height;
}
/**
* Crop image.
*
* @param image
* the image
* @param size
* the size
* @param resize
* Force-resize the image?
* @return the cropped image
*/
private PImage cropImage(PImage image, int size, boolean resize) {
PImage workingCopy;
try {
workingCopy = (PImage) image.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Cloning not supported!");
workingCopy = image;
}
// Crops (and resizes) an Image to fit into the suqare
PImage returnImage = app.createImage(size, size, PConstants.RGB);
// Resize Image
if (resize || workingCopy.width < size || workingCopy.height < size) {
if (workingCopy.width < workingCopy.height) {
workingCopy
.resize(size,
(int) ((float) workingCopy.height / ((float) workingCopy.width / (float) size)));
} else {
workingCopy
.resize((int) ((float) workingCopy.width / ((float) workingCopy.height / (float) size)),
size);
}
}
// Crop Starting Points
int x = (workingCopy.width / 2) - (size / 2);
int y = (workingCopy.height / 2) - (size / 2);
// Bugfixing: Don't Allow Out-of-Bounds coordinates
if (x + size > workingCopy.width)
x = workingCopy.width - size;
if (x < 0)
x = 0;
if (x + size > workingCopy.width)
size = workingCopy.width - x;
if (y + size > workingCopy.height)
x = workingCopy.height - size;
if (y < 0)
y = 0;
if (y + size > workingCopy.height)
size = workingCopy.height - y;
// Crop Image
returnImage.copy(workingCopy, x, y, size, size, 0, 0, size, size);
return returnImage;
}
/**
* Gets the maximum font size for a certain width.
*
* @param strings
* the strings
* @param size
* the width
* @return the maximum font size
*/
private int getNecessaryFontSize(List<MenuItem> strings, float size) {
int maxNumberCharacters = 0;
for (MenuItem s : strings) {
if (s.getType() == MenuItem.TEXT) {
if (s.getMenuText().contains("\n")) {
for (String t : s.getMenuText().split("\n")) {
if (t.length() > maxNumberCharacters)
maxNumberCharacters = t.length();
}
} else {
if (s.getMenuText().length() > maxNumberCharacters)
maxNumberCharacters = s.getMenuText().length();
}
}
}
float spc = size / (float) maxNumberCharacters; // Space Per Character
int returnValue = (int) (-0.5 + 1.725 * spc); // Determined using Linear
// Regression
return returnValue;
}
/**
* Gets the relevant items.
*
* @param items
* the items
* @return the relevant items
*/
private List<MenuItem> getRelevantItems(int[] items) {
List<MenuItem> returnList = new ArrayList<MenuItem>();
for (int i : items) {
if (i > 0 && i <= menuItems.size()) {
try {
returnList.add(menuItems.get(i + 1));
} catch (Exception e) {
}
}
}
return returnList;
}
/**
* Gets the relevant items.
*
* @param items
* the items
* @return the relevant items
*/
private List<MenuItem> getRelevantItems(List<MenuItem> items) {
List<MenuItem> returnList = new ArrayList<MenuItem>();
for (MenuItem i : items) {
if (menuItems.contains(i)) {
returnList.add(i);
}
}
return returnList;
}
/**
* Style children.
*/
private void styleChildren() {
int fontsize = getNecessaryFontSize(menuItems, size);
CSSStyle vss = this.getCssHelper().getVirtualStyleSheet();
CSSFont cf = this.getCssHelper().getVirtualStyleSheet().getCssfont().clone();
// Style Font: Bold + fitting fontsize
cf.setFontsize(fontsize);
cf.setWeight(CSSFontWeight.BOLD);
// Load Font
CSSFontManager cfm = new CSSFontManager(app);
IFont font = cfm.selectFont(cf);
for (MTRectangle c : menuContents) {
MTRectangle rect = c;
c.setWidthLocal(size);
c.setHeightLocal(size);
// Set Stroke/Border
rect.setStrokeColor(vss.getBorderColor());
rect.setStrokeWeight(vss.getBorderWidth());
// Set Font and Position for the child MTTextAreas
if (((MTRectangle) c).getTexture() == null) {
rect.setFillColor(vss.getBackgroundColor());
for (MTComponent d : c.getChildren()) {
if (d instanceof MTTextArea) {
MTTextArea ta = (MTTextArea) d;
ta.setFont(font);
}
}
float height = calcTotalHeight(c.getChildren());
float ypos = size / 2f - height / 2f;
for (MTComponent d : c.getChildren()) {
if (d instanceof MTTextArea) {
MTTextArea ta = (MTTextArea) d;
ta.setPositionRelativeToParent(new Vector3D(size / 2f,
ypos + ta.getHeightXY(TransformSpace.LOCAL)
/ 2f));
ypos += ta.getHeightXY(TransformSpace.LOCAL);
}
}
} else {
// Set FillColor for the image (neutral white)
rect.setFillColor(MTColor.WHITE);
}
this.addChild(c);
}
float xoffset = offset / 2f;
float yoffset = offset / 2f;
switch (position) {
case LEFT:
for (MTRectangle r : menuContents) {
r.setAnchor(PositionAnchor.UPPER_LEFT);
if (yoffset + r.getHeightXY(TransformSpace.LOCAL) > MT4jSettings
.getInstance().getWindowHeight()) {
xoffset += r.getWidthXY(TransformSpace.LOCAL) + offset;
yoffset = offset / 2f;
}
r.setPositionGlobal(new Vector3D(xoffset, yoffset));
yoffset += r.getHeightXY(TransformSpace.LOCAL) + offset;
}
break;
case RIGHT:
xoffset = MT4jSettings.getInstance().getWindowWidth() - size
- xoffset;
for (MTRectangle r : menuContents) {
r.setAnchor(PositionAnchor.UPPER_LEFT);
if (yoffset + r.getHeightXY(TransformSpace.LOCAL) > MT4jSettings
.getInstance().getWindowHeight()) {
xoffset -= r.getWidthXY(TransformSpace.LOCAL) + offset;
yoffset = offset / 2f;
}
r.setPositionGlobal(new Vector3D(xoffset, yoffset));
yoffset += r.getHeightXY(TransformSpace.LOCAL) + offset;
}
break;
case TOP:
for (MTRectangle r : menuContents) {
r.setAnchor(PositionAnchor.UPPER_LEFT);
if (xoffset + r.getWidthXY(TransformSpace.LOCAL) > MT4jSettings
.getInstance().getWindowWidth()) {
xoffset = offset / 2f;
yoffset += r.getHeightXY(TransformSpace.LOCAL) + offset;
}
r.setPositionGlobal(new Vector3D(xoffset, yoffset));
xoffset += r.getWidthXY(TransformSpace.LOCAL) + offset;
}
break;
case BOTTOM:
yoffset = MT4jSettings.getInstance().getWindowHeight() - size
- yoffset;
for (MTRectangle r : menuContents) {
r.setAnchor(PositionAnchor.UPPER_LEFT);
if (xoffset + r.getWidthXY(TransformSpace.LOCAL) > MT4jSettings
.getInstance().getWindowWidth()) {
xoffset = offset / 2f;
yoffset -= r.getHeightXY(TransformSpace.LOCAL) + offset;
}
r.setPositionGlobal(new Vector3D(xoffset, yoffset));
xoffset += r.getWidthXY(TransformSpace.LOCAL) + offset;
}
break;
default:
break;
}
}
}