package GuiTest; import java.awt.Font; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.Supplier; import java.util.stream.Stream; import javax.media.j3d.Appearance; import javax.media.j3d.BranchGroup; import javax.media.j3d.Font3D; import javax.media.j3d.FontExtrusion; import javax.media.j3d.Shape3D; import javax.media.j3d.Text3D; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.vecmath.Vector3d; /** * An implementation of a multi-line Text3D. * New lines and therefore instances of Text3D may be added during runtime. * Alignment may be set to left, right or center. * * @author basti */ public class MultiLineText3D extends BranchGroup { private final Font3D font3D; private final Appearance appearance; private final List text3DList; private final Vector3d pos; private final int distanceBetweenRows; private final double firstLineHeight; private final BiFunction getTextWidth; private final Font font; private final List setLinePositions; private final Map> alignmentMap; private String longestLine = ""; private int currentAlignment = Text3D.ALIGN_CENTER; private int lineCount; private double currentHeight; public MultiLineText3D(Font font, Appearance appearance, Vector3d pos, BiFunction getTextWidth) { this(new Font3D(font, new FontExtrusion()), appearance, pos, getTextWidth); } public MultiLineText3D(Font3D font3D, Appearance appearance, Vector3d pos, BiFunction getTextWidth) { this.font3D = font3D; this.distanceBetweenRows = font3D.getFont().getSize(); this.appearance = appearance; this.pos = pos; this.firstLineHeight = pos.y; this.text3DList = new ArrayList(); this.getTextWidth = getTextWidth; this.font = font3D.getFont(); this.setLinePositions = new ArrayList(); this.alignmentMap = new HashMap>(); this.currentHeight = firstLineHeight + distanceBetweenRows; init(); } /** * aligns all lines to the left */ public void alignAtLeft() { align(Text3D.ALIGN_FIRST); } /** * aligns all lines to the center (default) */ public void alignAtCenter() { align(Text3D.ALIGN_CENTER); } /** * aligns all lines to the right */ public void alignAtRight() { align(Text3D.ALIGN_LAST); } /** * updates text of a specified line * @param lineIndex index of line to be updated, starting with 0 * @param line new text for this line */ public void updateLine(int lineIndex, String line) { checkLineIndex(lineIndex); line = removeNewLines(line); text3DList.get(lineIndex).setString(line); updateLongestLine(); align(currentAlignment); } /** * sets text via single String * @param text may contain newLines */ public void setText(String text) { updateLines(text.split("\n")); } /** * sets text via array, newLines in individual Strings are omitted * @param lines array of Strings representing lines */ public void setText(String[] lines) { updateLines(lines); } /** * sets text via List, newLines in individual Strings are omitted * @param lines List of Strings representing lines */ public void setText(List lines) { updateLines(lines.toArray(new String[] {})); } private void checkLineIndex(int lineIndex) throws IllegalArgumentException { int lastIndex = lineCount - 1; if(lineIndex < 0 || lineIndex > lastIndex) { throw new IllegalArgumentException( "Line index must be in inclusive range: [0," + lastIndex + "]"); } } private void init() { setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND); setCapability(TransformGroup.ALLOW_CHILDREN_WRITE); fillAlignmentMap(); } private void updateLines(String[] lines) { this.lineCount = lines.length; if(lineCount > text3DList.size()) { addNewText3DInstances(lineCount); } updateLongestLine(lines); clearAllText(); for (int i = 0; i < lineCount; i++) { text3DList.get(i).setString(removeNewLines(lines[i])); } align(currentAlignment); } private String removeNewLines(String line) { return line.replace("\n", ""); } private void clearAllText() { text3DList.forEach(text3D -> text3D.setString("")); } private void updateLongestLine(String[] lines) { this.longestLine = Stream.of(lines) .max(sortLengthOfStringsAscending()) .orElseGet(getEmptyString()); } private void updateLongestLine() { this.longestLine = text3DList.stream() .map(text3D -> text3D.getString()) .max(sortLengthOfStringsAscending()) .orElseGet(getEmptyString()); } private Comparator sortLengthOfStringsAscending() { return (str1, str2) -> str1.length() < str2.length() ? -1 : str1.length() > str2.length() ? 1 : 0; } private Supplier getEmptyString() { return () -> ""; } private void addNewText3DInstances(int lineCount) { int delta = lineCount - text3DList.size(); for (int i = 0; i < delta; i++) { addNewText3DInstance(); } } private void addNewText3DInstance() { Shape3D shape = new Shape3D(); Text3D text3D = new Text3D(font3D); Vector3d linePos = new Vector3d(pos); linePos.y = currentHeight - distanceBetweenRows; currentHeight = linePos.y; TransformGroup lineTg = new TransformGroup(buildTransform3d(linePos)); BranchGroup bg = new BranchGroup(); lineTg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); bg.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND); bg.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE); setCapabilities(text3D); text3D.setAlignment(currentAlignment); shape.setGeometry(text3D); shape.setAppearance(appearance); lineTg.addChild(shape); bg.addChild(lineTg); addChild(bg); text3DList.add(text3D); setLinePositions.add(repositionLine(lineTg, linePos)); } private DoubleConsumer repositionLine(TransformGroup lineTg, Vector3d linePos) { return x -> { Vector3d newLinePos = new Vector3d(x, linePos.y, pos.z); lineTg.setTransform(buildTransform3d(newLinePos)); }; } private void setCapabilities(Text3D text3D) { text3D.setCapability(Text3D.ALLOW_INTERSECT); text3D.setCapability(Text3D.ALLOW_STRING_WRITE); text3D.setCapability(Text3D.ALLOW_POSITION_WRITE); text3D.setCapability(Text3D.ALLOW_ALIGNMENT_WRITE); text3D.setCapability(Text3D.ALLOW_FONT3D_WRITE); } private Transform3D buildTransform3d(Vector3d pos) { Transform3D t3d = new Transform3D(); t3d.setTranslation(pos); return t3d; } private void align(int alignment) { for (int i = 0; i < text3DList.size(); i++) { text3DList.get(i).setAlignment(alignment); alignmentMap.get(alignment).accept(setLinePositions.get(i)); } this.currentAlignment = alignment; } private void fillAlignmentMap() { alignmentMap.put(Text3D.ALIGN_FIRST, setLinePos -> setLinePos.accept(pos.x - halfOfLongestLine())); alignmentMap.put(Text3D.ALIGN_CENTER, setLinePos -> setLinePos.accept(pos.x)); alignmentMap.put(Text3D.ALIGN_LAST, setLinePos -> setLinePos.accept(pos.x + halfOfLongestLine())); } private double halfOfLongestLine() { return getTextWidth.apply(longestLine, font) / 2; } }