Java tutorial
// // $Id: Label.java,v 1.2 2007/04/27 19:46:29 vivaldi Exp $ // // BUI - a user interface library for the JME 3D engine // Copyright (C) 2005-2006, Michael Bayne, All Rights Reserved // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.jmex.bui; import com.jme.renderer.ColorRGBA; import com.jme.renderer.Renderer; import com.jmex.bui.enumeratedConstants.HorizontalAlignment; import com.jmex.bui.enumeratedConstants.Orientation; import com.jmex.bui.enumeratedConstants.TextEffect; import com.jmex.bui.icon.BIcon; import com.jmex.bui.text.BText; import com.jmex.bui.text.BTextFactory; import com.jmex.bui.util.Dimension; import com.jmex.bui.util.Insets; import com.jmex.bui.util.Rectangle; import org.lwjgl.opengl.GL11; /** * Handles the underlying layout and rendering for {@link BLabel} and {@link BButton}. */ public class Label { public Label(BTextComponent container) { _container = container; } /** * Updates the text displayed by this label. * * @param text String */ public void setText(String text) { if (_value != null && _value.equals(text)) { return; } _value = text; // clear out our old text config and texture releaseText(); // our size may have changed so we need to revalidate _container.invalidate(); } /** * Returns the text currently being displayed by this label. * * @return String */ public String getText() { return _value; } /** * Configures the label to display the specified icon. * * @param icon BIcon */ public void setIcon(BIcon icon) { if (_icon == icon) { return; } int owidth = 0, oheight = 0, nwidth = 0, nheight = 0; if (_icon != null) { owidth = _icon.getWidth(); oheight = _icon.getHeight(); if (_container.isAdded()) { _icon.wasRemoved(); } } _icon = icon; if (_icon != null) { nwidth = _icon.getWidth(); nheight = _icon.getHeight(); if (_container.isAdded()) { _icon.wasAdded(); } } if (owidth != nwidth || oheight != nheight) { // reset our config so that we force a text reflow to account for the changed icon size releaseText(); _container.invalidate(); } else if (_container.isValid()) { _container.layout(); } } /** * Returns the icon being displayed by this label. * * @return BIcon */ public BIcon getIcon() { return _icon; } /** * Configures the gap between the icon and the text. * * @param gap int */ public void setIconTextGap(int gap) { _gap = gap; } /** * Returns the gap between the icon and the text. * * @return int */ public int getIconTextGap() { return _gap; } /** * Sets the orientation of this label with respect to its icon. If the horizontal (the default) the text is * displayed to the right of the icon, if vertical the text is displayed below it. * * @param orient {@link com.jmex.bui.enumeratedConstants.Orientation} */ public void setOrientation(Orientation orient) { _orient = orient; if (_container.isAdded()) { _container.layout(); } } /** * Configures whether this label will wrap, truncate or scale if it cannot fit text into its allotted width. The * default is to wrap. * * @param mode BLabel.Fit */ public void setFit(BLabel.Fit mode) { _fit = mode; } /** * Called by our containing component when it was added to the interface hierarchy. */ public void wasAdded() { if (_icon != null) { _icon.wasAdded(); } if (_config != null && _config.glyphs != null) { _config.glyphs.wasAdded(); } } /** * Called by our containing component when it was removed from the interface hierarchy. */ public void wasRemoved() { if (_icon != null) { _icon.wasRemoved(); } if (_config != null && _config.glyphs != null) { _config.glyphs.wasRemoved(); } } /** * Computes the preferred size of the label. * * @param whint int * @param hhint int * @return Dimension */ public Dimension computePreferredSize(int whint, int hhint) { // if our cached preferred size is not valid, recompute it Config prefconfig = layoutConfig(_prefconfig, whint > 0 ? whint : Short.MAX_VALUE - 1); _prefsize = computeSize(_prefconfig = prefconfig); prefconfig.glyphs = null; // we don't need to retain these return new Dimension(_prefsize); } /** * Lays out the label text and icon. * * @param insets Insets * @param contWidth int * @param contHeight int */ public void layout(Insets insets, int contWidth, int contHeight) { // compute any offsets needed to center or align things Config config = layoutConfig(_config, contWidth - insets.getHorizontal()); Dimension size = computeSize(config); int xoff = 0, yoff = 0; switch (_orient) { case HORIZONTAL: if (_icon != null) { _ix = getXOffset(insets, contWidth, size.width); _iy = getYOffset(insets, contHeight, _icon.getHeight()); xoff = (_icon.getWidth() + _gap); } if (config.glyphs != null) { _tx = getXOffset(insets, contWidth, size.width) + xoff; _ty = getYOffset(insets, contHeight, config.glyphs.size.height); } break; case VERTICAL: if (config.glyphs != null) { _tx = getXOffset(insets, contWidth, config.glyphs.size.width); _ty = getYOffset(insets, contHeight, size.height); yoff = (config.glyphs.size.height + _gap); } if (_icon != null) { _ix = getXOffset(insets, contWidth, _icon.getWidth()); _iy = getYOffset(insets, contHeight, size.height) + yoff; } break; case OVERLAPPING: if (_icon != null) { _ix = getXOffset(insets, contWidth, _icon.getWidth()); _iy = getYOffset(insets, contHeight, _icon.getHeight()); } if (config.glyphs != null) { _tx = getXOffset(insets, contWidth, config.glyphs.size.width); _ty = getYOffset(insets, contHeight, config.glyphs.size.height); } break; } useConfig(config); } /** * Releases any underlying texture resources created by this label. */ public void releaseText() { if (_config != null && _config.glyphs != null) { if (_container.isAdded()) { _config.glyphs.wasRemoved(); } _config = null; } } /** * Renders the label text and icon. * * @param renderer Renderer * @param x int * @param y int * @param contWidth int * @param contHeight int * @param alpha float */ public void render(Renderer renderer, int x, int y, int contWidth, int contHeight, float alpha) { GL11.glTranslatef(x, y, 0); try { if (_icon != null) { _icon.render(renderer, _ix, _iy, alpha); } if (_config != null && _config.glyphs != null) { renderText(renderer, contWidth, contHeight, alpha); } } finally { GL11.glTranslatef(-x, -y, 0); } } /** * @param renderer Renderer * @param contWidth int * @param contHeight int * @param alpha float */ protected void renderText(Renderer renderer, int contWidth, int contHeight, float alpha) { final HorizontalAlignment horzAlignment = _container.getHorizontalAlignment(); if (_fit == BLabel.Fit.WRAP) { _config.glyphs.render(renderer, _tx, _ty, horzAlignment, alpha, _config.spacing); return; } Insets insets = _container.getInsets(); int width = contWidth - insets.getHorizontal(); int height = contHeight - insets.getVertical(); if (width <= 0 || height <= 0) { return; } if (_fit == BLabel.Fit.SCALE) { _config.glyphs.render(renderer, _tx, _ty, width, height, horzAlignment, alpha); return; } boolean scissored = BComponent.intersectScissorBox(_srect, _container.getAbsoluteX() + insets.left, _container.getAbsoluteY() + insets.bottom, width, height); try { _config.glyphs.render(renderer, _tx, _ty, horzAlignment, alpha, _config.spacing); } finally { BComponent.restoreScissorState(scissored, _srect); } } /** * @param config Config * @return Dimension */ protected Dimension computeSize(Config config) { int iwidth = 0, iheight = 0, twidth = 0, theight = 0, gap = 0; if (_icon != null) { iwidth = _icon.getWidth(); iheight = _icon.getHeight(); } if (config.glyphs != null) { if (_icon != null) { gap = _gap; } twidth = config.glyphs.size.width; theight = config.glyphs.size.height; } int width, height; switch (_orient) { default: case HORIZONTAL: width = iwidth + gap + twidth; height = Math.max(iheight, theight); break; case VERTICAL: width = Math.max(iwidth, twidth); height = iheight + gap + theight; break; case OVERLAPPING: width = Math.max(iwidth, twidth); height = Math.max(iheight, theight); break; } return new Dimension(width, height); } /** * @param insets Insets * @param contWidth int * @param width int * @return int */ protected int getXOffset(Insets insets, int contWidth, int width) { switch (_container.getHorizontalAlignment()) { default: case LEFT: return insets.left; case RIGHT: return contWidth - width - insets.right; case CENTER: return (contWidth - insets.getHorizontal() - width) / 2 + insets.left; } } /** * @param insets Insets * @param contHeight int * @param height int * @return int */ protected int getYOffset(Insets insets, int contHeight, int height) { switch (_container.getVerticalAlignment()) { default: case TOP: return contHeight - height - insets.top; case BOTTOM: return insets.bottom; case CENTER: return (contHeight - insets.getVertical() - height) / 2 + insets.bottom; } } /** * Creates glyphs for the current text at the specified target width. * * @param oconfig Config * @param twidth int * @return Config */ protected Config layoutConfig(Config oconfig, int twidth) { // if we're not wrapping, force our target width if (_fit != BLabel.Fit.WRAP) { twidth = Short.MAX_VALUE - 1; } if (_value != null) { // account for the space taken up by the icon if (_icon != null && _orient == Orientation.HORIZONTAL) { twidth -= _gap; twidth -= _icon.getWidth(); } } // no need to recreate our glyphs if our config hasn't changed Config config = _container.getLabelConfig(this, twidth); if (oconfig != null && oconfig.glyphs != null && oconfig.matches(config, twidth)) { return oconfig; } // if we have no text, we're done if (_value == null || _value.equals("")) { return config; } // sanity check if (twidth < 0) { Log.log.warning( "Requested to layout with negative target width [text=" + _value + ", twidth=" + twidth + "]."); Thread.dumpStack(); return config; } // render up some new text BTextFactory tfact = _container.getTextFactory(); Text text = new Text(); text.lines = tfact.wrapText(_value, config.color, config.effect, config.effectSize, config.effectColor, twidth); for (int ii = 0; ii < text.lines.length; ii++) { final Dimension size = text.lines[ii].getSize(); text.size.width = Math.max(text.size.width, size.width); text.size.height += size.height + (ii > 0 ? config.spacing : 0); } config.glyphs = text; // if our old config is the same number of lines as our new config, expand the width region // that this configuration will match if (oconfig != null && oconfig.glyphs != null && oconfig.glyphs.lines.length == config.glyphs.lines.length) { config.minwidth = Math.min(config.minwidth, oconfig.minwidth); config.maxwidth = Math.max(config.maxwidth, oconfig.maxwidth); } return config; } /** * @param config Config */ protected void useConfig(Config config) { // make sure it's not the one we're already using if (_config == config) { return; } // clear out any previous rendered text releaseText(); // note our new config _config = config; if (_config != null && _config.glyphs != null && _container.isAdded()) { _config.glyphs.wasAdded(); } } /** * */ protected static class Config { public String text; public ColorRGBA color; public TextEffect effect; public int effectSize; public ColorRGBA effectColor; public int minwidth, maxwidth; public Text glyphs; public int spacing; /** * @param other Config * @param twidth int * @return boolean */ public boolean matches(Config other, int twidth) { if (other == null) { return false; } // if any of the styles are different, we don't match if (effect != other.effect) { return false; } //Is this trying to do an object comparison? //todo: re-review if (text != other.text && (text == null || !text.equals(other.text))) { return false; } if (!color.equals(other.color)) { return false; } if (effectColor != other.effectColor && (effectColor == null || !effectColor.equals(other.effectColor))) { return false; } if (effectSize != other.effectSize || spacing != other.spacing) { return false; } // if we are only one line we are fine as long as we're less than or equal to the // target width (only if it is smaller than our minwidth might it cause us to wrap) if (glyphs != null && glyphs.lines.length == 1 && minwidth <= twidth) { return true; } // otherwise the new target width has to fall within our handled range return (minwidth <= twidth) && (twidth <= maxwidth); } /** * @return String */ @Override public String toString() { return text + "(" + toString(color) + "," + effect + "," + toString(effectColor) + "," + minwidth + "<>" + maxwidth + ")"; } /** * @param color ColorRGBA * @return String */ protected String toString(ColorRGBA color) { return color == null ? "null" : Integer.toHexString(color.hashCode()); } } protected static class Text { public BText[] lines; public Dimension size = new Dimension(); /** * @param renderer Renderer * @param tx int * @param ty int * @param halign int * @param alpha float * @param spacing int */ public void render(Renderer renderer, int tx, int ty, HorizontalAlignment halign, float alpha, int spacing) { // render the lines from the bottom up for (int ii = lines.length - 1; ii >= 0; ii--) { int lx = tx; final BText line = lines[ii]; final Dimension lineSize = line.getSize(); if (halign == HorizontalAlignment.RIGHT) { lx += size.width - lineSize.width; } else if (halign == HorizontalAlignment.CENTER) { lx += (size.width - lineSize.width) / 2; } line.render(renderer, lx, ty, alpha); ty += lineSize.height + (ii > 0 ? spacing : 0); } } /** * @param renderer Renderer * @param tx int * @param ty int * @param width int * @param height int * @param halign int * @param alpha float */ public void render(Renderer renderer, int tx, int ty, int width, int height, HorizontalAlignment halign, float alpha) { // render only the first line float scale = 1f; if (size.width > width) { scale = (float) width / size.width; } if (size.height > height) { scale = Math.min(scale, (float) height / size.height); } width = (int) (size.width * scale); height = (int) (size.height * scale); if (height < size.height) { ty += (size.height - height) / 2; } if (halign == HorizontalAlignment.RIGHT) { tx += size.width - width; } else if (halign == HorizontalAlignment.CENTER) { tx += (size.width - width) / 2; } lines[0].render(renderer, tx, ty, width, height, alpha); } /** * */ public void wasAdded() { for (BText line : lines) { line.wasAdded(); } } /** * */ public void wasRemoved() { for (BText line : lines) { line.wasRemoved(); } } } protected BTextComponent _container; protected String _value; protected Orientation _orient = Orientation.HORIZONTAL; protected int _gap = 3; protected BLabel.Fit _fit = BLabel.Fit.WRAP; protected BIcon _icon; protected int _ix, _iy; protected Config _config; protected int _tx, _ty; protected float _alpha = 1f; protected Config _prefconfig; protected Dimension _prefsize; protected Rectangle _srect = new Rectangle(); }