PageRenderTime 74ms CodeModel.GetById 29ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

/extensions/org/mt4jx/components/visibleComponents/widgets/menus/MTSquareMenu.java

http://mt4j.googlecode.com/
Java | 627 lines | 383 code | 112 blank | 132 comment | 64 complexity | 355c340bb8e705b7f6b8ce289429bf00 MD5 | raw file
  1package org.mt4jx.components.visibleComponents.widgets.menus;
  2
  3
  4import java.util.ArrayList;
  5import java.util.List;
  6
  7import org.mt4j.MTApplication;
  8import org.mt4j.components.MTComponent;
  9import org.mt4j.components.TransformSpace;
 10import org.mt4j.components.clipping.Clip;
 11import org.mt4j.components.css.style.CSSFont;
 12import org.mt4j.components.css.style.CSSStyle;
 13import org.mt4j.components.css.util.CSSFontManager;
 14import org.mt4j.components.css.util.CSSKeywords.CSSFontWeight;
 15import org.mt4j.components.css.util.CSSStylableComponent;
 16import org.mt4j.components.visibleComponents.font.IFont;
 17import org.mt4j.components.visibleComponents.shapes.MTPolygon;
 18import org.mt4j.components.visibleComponents.shapes.MTRectangle;
 19import org.mt4j.components.visibleComponents.widgets.MTTextArea;
 20import org.mt4j.input.inputProcessors.IGestureEventListener;
 21import org.mt4j.input.inputProcessors.MTGestureEvent;
 22import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapEvent;
 23import org.mt4j.input.inputProcessors.componentProcessors.tapProcessor.TapProcessor;
 24import org.mt4j.util.MTColor;
 25import org.mt4j.util.math.Vector3D;
 26import org.mt4j.util.math.Vertex;
 27
 28import processing.core.PConstants;
 29import processing.core.PImage;
 30
 31/**
 32 * The Class MTSquareMenu.
 33 * Menu that contains Squares as buttons containing text or images
 34 */
 35public class MTSquareMenu extends MTRectangle implements CSSStylableComponent {
 36
 37	/** The app. */
 38	private MTApplication app;
 39	
 40	/** The menu contents. */
 41	private List<MTRectangle> menuContents = new ArrayList<MTRectangle>();
 42	
 43	/** The layout. */
 44	private List<ArrayList<MTRectangle>> layout = new ArrayList<ArrayList<MTRectangle>>();
 45	
 46	/** The size. */
 47	private float size;
 48	
 49	/** The current item. */
 50	private int current = 0;
 51
 52	/** The max per line. */
 53	private int maxPerLine = 0;
 54	
 55	/** The bezel. */
 56	private float bezel = 10f;
 57	
 58	// List of the Child Polygons and their IGestureEventListeners
 59	/** The polygon listeners. */
 60	private List<PolygonListeners> polygonListeners = new ArrayList<PolygonListeners>();
 61	
 62	/** The menu items. */
 63	private List<MenuItem> menuItems = new ArrayList<MenuItem>();
 64	
 65	/**
 66	 * Instantiates a new MTSquareMenu
 67	 *
 68	 * @param app the app
 69	 * @param position the position of the Menu
 70	 * @param menuItems the menu items
 71	 * @param size the size of the squares
 72	 */
 73	public MTSquareMenu(MTApplication app, Vector3D position,
 74			List<MenuItem> menuItems, float size) {
 75		super(app, position.x, position.y, (float) (int) Math
 76						.sqrt(menuItems.size() + 1) * size, (float) (int) Math
 77				.sqrt(menuItems.size() + 1) * size);
 78		this.app = app;
 79		this.size = size;
 80		
 81		// Set the Rectangle to be invisible
 82		this.setCssForceDisable(true);
 83		this.setNoFill(true);
 84		this.setNoStroke(true);
 85
 86		this.menuItems = menuItems;
 87		this.createMenuItems();
 88
 89		//Register the TapProcessor
 90		this.setGestureAllowance(TapProcessor.class, true);
 91		this.registerInputProcessor(new TapProcessor(app));
 92		this.addGestureListener(TapProcessor.class, new TapListener(polygonListeners));
 93		this.setCssForceDisable(true);
 94		
 95		
 96
 97	}
 98
 99	public void createMenuItems() {
100		for (MTComponent c : this.getChildren()) {
101			c.destroy();
102		}
103		menuContents.clear();
104		polygonListeners.clear();
105		
106		for (MenuItem s : menuItems) {
107			
108			if (s != null && s.getType() == MenuItem.TEXT) {
109				//Create a new menu cell
110				MTRectangle container = new MTRectangle(app, 0, 0, size, size);
111				this.addChild(container);
112
113				// Add MTTextArea Children to take single lines of the Menu Text
114				for (String t : s.getMenuText().split("\n")) {
115					MTTextArea menuItem = new MTTextArea(app);
116					menuItem.setText(t);
117					menuItem.setCssForceDisable(true);
118					menuItem.setFillColor(new MTColor(0, 0, 0, 0));
119					menuItem.setStrokeColor(new MTColor(0, 0, 0, 0));
120					menuItem.setPickable(false);
121					container.addChild(menuItem);
122
123				}
124
125				container.setChildClip(new Clip(container));
126				container.setPickable(false);
127				polygonListeners.add(new PolygonListeners(container, s.getGestureListener()));
128				menuContents.add(container);
129			} else if (s != null && s.getType() == MenuItem.PICTURE) {
130
131				if (s.getMenuImage() != null) {
132					PImage texture = null;
133					// If Image doesn't fit, make it fit!
134					if (s.getMenuImage().width != (int) size
135							|| s.getMenuImage().height != (int) size) {
136						texture = cropImage(s.getMenuImage(), (int) size, true);
137					} else {
138						texture = s.getMenuImage();
139					}
140
141					
142					//Create a new menu cell
143					MTRectangle container = new MTRectangle(app, 0, 0, size,
144							size);
145					this.addChild(container);
146					//Set the background texture
147					container.setTexture(texture);
148
149					container.setChildClip(new Clip(container));
150					container.setPickable(false);
151					polygonListeners.add(new PolygonListeners(container, s.getGestureListener()));
152					menuContents.add(container);
153				}
154
155			}
156
157		}
158		
159		//Apply Style to Children
160		this.styleChildren(getNecessaryFontSize(menuItems, size));
161
162	}
163
164
165	/**
166	 * Gets the size.
167	 *
168	 * @return the size
169	 */
170	public float getSize() {
171		return size;
172	}
173	
174	
175	/**
176	 * Sets the menu items and reinstantiates the menu
177	 *
178	 * @param menuItems the new menu items
179	 */
180	public void setMenuItems(List<MenuItem> menuItems) {
181		this.menuItems = menuItems;
182		createMenuItems();
183	}
184
185
186
187	/**
188	 * Sets the size.
189	 *
190	 * @param size the new size
191	 */
192	public void setSize(float size) {
193		this.size = size;
194		this.createMenuItems();
195	}
196
197	/**
198	 * Calculates the total height of a number of MTTextAreas
199	 *
200	 * @param components the components
201	 * @return the height
202	 */
203	private float calcTotalHeight(MTComponent[] components) {
204		float height = 0;
205		for (MTComponent c : components) {
206			if (c instanceof MTTextArea)
207				height += ((MTTextArea) c).getHeightXY(TransformSpace.LOCAL);
208		}
209
210		return height;
211	}
212
213	/**
214	 * Crop image.
215	 *
216	 * @param image the image
217	 * @param size the size
218	 * @param resize Force-resize the image?
219	 * @return the cropped image
220	 */
221	private PImage cropImage(PImage image, int size, boolean resize) {
222		PImage workingCopy;
223		try {
224			workingCopy= (PImage) image.clone();
225		} catch (CloneNotSupportedException e) {
226			System.out.println("Cloning not supported!");
227			workingCopy = image;
228		}
229		
230		
231		
232		//Crops (and resizes) an Image to fit into the suqare
233		PImage returnImage = app.createImage(size, size, PConstants.RGB);
234		
235		//Resize Image
236		if (resize || workingCopy.width < size || workingCopy.height < size) {
237			if (workingCopy.width < workingCopy.height) {
238				workingCopy.resize(
239						size,
240						(int) ((float) workingCopy.height / ((float) workingCopy.width / (float) size)));
241			} else {
242				workingCopy.resize(
243						(int) ((float) workingCopy.width / ((float) workingCopy.height / (float) size)),
244						size);
245			}
246
247		}
248		
249		// Crop Starting Points
250		int x = (workingCopy.width / 2) - (size / 2);
251		int y = (workingCopy.height / 2) - (size / 2);
252		
253		// Bugfixing: Don't Allow Out-of-Bounds coordinates
254		if (x + size > workingCopy.width)
255			x = workingCopy.width - size;
256		if (x < 0)
257			x = 0;
258		if (x + size > workingCopy.width)
259			size = workingCopy.width - x;
260		if (y + size > workingCopy.height)
261			x = workingCopy.height - size;
262		if (y < 0)
263			y = 0;
264		if (y + size > workingCopy.height)
265			size = workingCopy.height - y;
266		
267		//Crop Image
268		returnImage.copy(workingCopy, x, y, size, size, 0, 0, size, size);
269
270		return returnImage;
271	}
272
273	/**
274	 * Gets the maximum font size for a certain width
275	 *
276	 * @param strings the strings
277	 * @param size the width
278	 * @return the maximum font size
279	 */
280	private int getNecessaryFontSize(List<MenuItem> strings, float size) {
281		int maxNumberCharacters = 0;
282
283		for (MenuItem s : strings) {
284
285			if (s.getType() == MenuItem.TEXT) {
286				if (s.getMenuText().contains("\n")) {
287					for (String t : s.getMenuText().split("\n")) {
288
289						if (t.length() > maxNumberCharacters)
290							maxNumberCharacters = t.length();
291
292					}
293				} else {
294
295					if (s.getMenuText().length() > maxNumberCharacters)
296						maxNumberCharacters = s.getMenuText().length();
297
298				}
299			}
300		}
301
302		float spc = size / (float) maxNumberCharacters; // Space Per Character
303		int returnValue = (int)(-0.5 + 1.725 * spc); //Determined using Linear Regression
304		return returnValue;
305	}
306
307	/**
308	 * Returns the next n items
309	 *
310	 * @param next the number of items to return
311	 * @return the list of n next items
312	 */
313	private List<MTRectangle> next(int next) {
314		List<MTRectangle> returnValues = new ArrayList<MTRectangle>();
315		for (int i = 0; i < next; i++) {
316			returnValues.add(menuContents.get(current++));
317		}
318		return returnValues;
319	}
320	
321	/**
322	 * Distribute the menu cells on the rows.
323	 */
324	private void organizeRectangles() {
325		layout.clear();
326		layout.add(new ArrayList<MTRectangle>());
327		layout.add(new ArrayList<MTRectangle>());
328		layout.add(new ArrayList<MTRectangle>());
329		layout.add(new ArrayList<MTRectangle>());
330		current = 0;
331		switch (menuContents.size()) {
332		case 0:
333		case -1:
334			break;
335
336		case 1:
337			layout.get(0).addAll(next(1));
338			maxPerLine = 1;
339			break;
340		case 2:
341			layout.get(0).addAll(next(2));
342			maxPerLine = 2;
343			break;
344		case 3:
345			layout.get(0).addAll(next(1));
346			layout.get(1).addAll(next(2));
347			maxPerLine = 2;
348			break;
349		case 4:
350			layout.get(0).addAll(next(2));
351			layout.get(1).addAll(next(2));
352			maxPerLine = 2;
353			break;
354		case 5:
355			layout.get(0).addAll(next(2));
356			layout.get(1).addAll(next(3));
357			maxPerLine = 3;
358			break;
359		case 6:
360			layout.get(0).addAll(next(3));
361			layout.get(1).addAll(next(3));
362			maxPerLine = 3;
363			break;
364		case 7:
365			layout.get(0).addAll(next(2));
366			layout.get(1).addAll(next(3));
367			layout.get(2).addAll(next(2));
368			maxPerLine = 3;
369			break;
370		case 8:
371			layout.get(0).addAll(next(3));
372			layout.get(1).addAll(next(2));
373			layout.get(2).addAll(next(3));
374			maxPerLine = 3;
375			break;
376		case 9:
377			layout.get(0).addAll(next(3));
378			layout.get(1).addAll(next(3));
379			layout.get(2).addAll(next(3));
380			maxPerLine = 3;
381			break;
382		case 10:
383			layout.get(0).addAll(next(3));
384			layout.get(1).addAll(next(4));
385			layout.get(2).addAll(next(3));
386			maxPerLine = 4;
387			break;
388		case 11:
389			layout.get(0).addAll(next(4));
390			layout.get(1).addAll(next(3));
391			layout.get(2).addAll(next(4));
392			maxPerLine = 4;
393			break;
394		case 12:
395			layout.get(0).addAll(next(4));
396			layout.get(1).addAll(next(4));
397			layout.get(2).addAll(next(4));
398			maxPerLine = 4;
399			break;
400		case 13:
401			layout.get(0).addAll(next(4));
402			layout.get(1).addAll(next(5));
403			layout.get(2).addAll(next(4));
404			maxPerLine = 5;
405			break;
406		case 14:
407			layout.get(0).addAll(next(3));
408			layout.get(1).addAll(next(4));
409			layout.get(2).addAll(next(4));
410			layout.get(3).addAll(next(3));
411			maxPerLine = 4;
412			break;
413		case 15:
414			layout.get(0).addAll(next(5));
415			layout.get(1).addAll(next(5));
416			layout.get(2).addAll(next(5));
417			maxPerLine = 5;
418			break;
419		case 16:
420			layout.get(0).addAll(next(4));
421			layout.get(1).addAll(next(4));
422			layout.get(2).addAll(next(4));
423			layout.get(3).addAll(next(4));
424			maxPerLine = 4;
425			break;
426		default:{
427				System.err.println("Unsupported number of menu items in: " + this);
428			}
429		}
430
431	}
432	
433
434	
435	
436	/**
437	 * Style the cells of the menu.
438	 *
439	 * @param fontsize the font-size
440	 */
441	private void styleChildren(int fontsize) {
442		organizeRectangles();
443		CSSStyle vss = this.getCssHelper().getVirtualStyleSheet();
444		CSSFont cf = this.getCssHelper().getVirtualStyleSheet().getCssfont().clone();
445		// Style Font: Bold + fitting fontsize
446		cf.setFontsize(fontsize);
447		cf.setWeight(CSSFontWeight.BOLD);
448		
449		//Load Font
450		CSSFontManager cfm = new CSSFontManager(app);
451		IFont font = cfm.selectFont(cf);
452
453		for (MTRectangle c : menuContents) {
454
455			MTRectangle rect = c;
456
457			c.setWidthLocal(size);
458			c.setHeightLocal(size);
459			
460			
461			//Set Stroke/Border
462			rect.setStrokeColor(vss.getBorderColor());
463			rect.setStrokeWeight(vss.getBorderWidth());
464
465			// Set Font and Position for the child MTTextAreas
466			if (((MTRectangle) c).getTexture() == null) {
467				rect.setFillColor(vss.getBackgroundColor());
468				for (MTComponent d : c.getChildren()) {
469					if (d instanceof MTTextArea) {
470						MTTextArea ta = (MTTextArea) d;
471						ta.setFont(font);
472					}
473				}
474
475				float height = calcTotalHeight(c.getChildren());
476				float ypos = size / 2f - height / 2f;
477				for (MTComponent d : c.getChildren()) {
478					if (d instanceof MTTextArea) {
479						MTTextArea ta = (MTTextArea) d;
480
481						ta.setPositionRelativeToParent(new Vector3D(size / 2f,
482								ypos + ta.getHeightXY(TransformSpace.LOCAL)
483										/ 2f));
484						ypos += ta.getHeightXY(TransformSpace.LOCAL);
485
486					}
487
488				}
489			} else {
490				//Set FillColor for the image (neutral white)
491				rect.setFillColor(MTColor.WHITE);
492			}
493
494		}
495
496		//Min/Max Values of the Children
497		float minx = 16000, maxx = -16000, miny = 16000, maxy = -16000;
498		int currentRow = 0;
499		
500		// Position the Polygons in the grid
501		for (List<MTRectangle> lr : layout) {
502			int currentColumn = 0;
503			for (MTRectangle r : lr) {
504				r.setPositionRelativeToParent((new Vector3D(this
505						.getVerticesLocal()[0].x
506						+ (size / 2f)
507						+ (bezel / 2f)
508						+ currentColumn++
509						* (size + bezel)
510						+ (maxPerLine - lr.size()) * (size / 2f + bezel / 2f),
511						this.getVerticesLocal()[0].x + (size / 2 + bezel / 2f)
512								+ currentRow * (size + bezel))));
513				//Determine Min/Max-Positions
514				//We have to use the childrens relative-to parent vertices for the calculation of this component's local vertices
515				Vertex[] unTransformedCopy = Vertex.getDeepVertexArrayCopy(r.getGeometryInfo().getVertices());
516				//transform the copied vertices and save them in the vertices array
517				Vertex[] verticesRelParent = Vertex.transFormArray(r.getLocalMatrix(), unTransformedCopy);
518				for (Vertex v: verticesRelParent) {
519					if (v.x < minx) minx = v.x;
520					if (v.x > maxx) maxx = v.x;
521					if (v.y < miny) miny = v.y;
522					if (v.y > maxy) maxy = v.y;
523				}
524			}
525			currentRow++;
526		}
527		
528		MTColor fill = this.getFillColor();
529		//Set Vertices to include all children
530		this.setVertices(new Vertex[] {new Vertex(minx,miny, 0, fill.getR(), fill.getG(), fill.getB(), fill.getAlpha()), new Vertex(maxx,miny, 0, fill.getR(), fill.getG(), fill.getB(), fill.getAlpha()), new Vertex(maxx,maxy, 0, fill.getR(), fill.getG(), fill.getB(), fill.getAlpha()), new Vertex(minx,maxy, 0, fill.getR(), fill.getG(), fill.getB(), fill.getAlpha()),new Vertex(minx,miny, 0, fill.getR(), fill.getG(), fill.getB(), fill.getAlpha())});
531	}
532
533	
534
535
536	/**
537	 * The listener interface for receiving tap events.
538	 * The class that is interested in processing a tap
539	 * event implements this interface, and the object created
540	 * with that class is registered with a component using the
541	 * component's <code>addTapListener<code> method. When
542	 * the tap event occurs, that object's appropriate
543	 * method is invoked.
544	 *
545	 * @see TapEvent
546	 */
547	public class TapListener implements IGestureEventListener {
548		//Tap Listener to reach through TapListeners to children
549		
550		/** The children. */
551		List<PolygonListeners> children;
552		
553		/**
554		 * Instantiates a new tap listener.
555		 *
556		 * @param children the children
557		 */
558		public TapListener(List<PolygonListeners> children) {
559			this.children = children;
560		}
561		
562		
563		/* (non-Javadoc)
564		 * @see org.mt4j.input.inputProcessors.IGestureEventListener#processGestureEvent(org.mt4j.input.inputProcessors.MTGestureEvent)
565		 */
566		public boolean processGestureEvent(MTGestureEvent ge) {
567			if (ge instanceof TapEvent) {
568				
569				TapEvent te = (TapEvent)ge;
570				if (te.getTapID() == TapEvent.TAPPED) {
571					//Vector3D w = Tools3D.project(app, app.getCurrentScene().getSceneCam(), te.getLocationOnScreen());
572					for (PolygonListeners pl: children) {
573						pl.component.setPickable(true);
574						if (
575//								pl.component.getIntersectionGlobal(Tools3D
576//								.getCameraPickRay(app, pl.component, te.getCursor().getPosition().x,
577//										te.getCursor().getPosition().y)) != null
578								pl.component.getIntersectionGlobal(te.getCursor()) != null
579										
580						) {
581							pl.listener.processGestureEvent(ge);
582						} else {
583					
584						}
585						pl.component.setPickable(false);
586					}
587				}
588			}
589			return false;
590		}
591		
592		
593		
594		
595	}
596
597
598
599
600	/**
601	 * The Class PolygonListeners.
602	 */
603	public class PolygonListeners {
604		
605		/** The component. */
606		public MTPolygon component;
607		
608		/** The listener. */
609		public IGestureEventListener listener;
610		
611		/**
612		 * Instantiates a new polygon listeners.
613		 *
614		 * @param component the component
615		 * @param listener the listener
616		 */
617		public PolygonListeners(MTPolygon component, IGestureEventListener listener) {
618			this.component = component;
619			this.listener = listener;
620		}
621		
622	}
623	
624	
625	
626}
627