/extensions/org/mt4jx/components/visibleComponents/widgets/menus/MTSquareMenu.java
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