/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

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