I've got a little problem with aligning a group of Strings to the bottom of a box in Java.
NOTE: the
Box
class I'm using is not a default > javax.Swing box! It's a simple costum box with a x, y position, and with a width and height!
Message
class which can be individually aligned by my Allign
class.MessageList
object containing an ArrayList of Message
objects which can be aligned together with the Allign
class.Box
object that contains the position and dimension of the box. The Allign
class uses this box to align the objects in.Allign
class that can align different types of Objects, and use a Box
object to align in.(code snippets further down the page)
The Message
object can use different Font
settings. The Allign
class can align these Message
s objects. The Message
class contains a method called obtainFontDimension()
, which gets the bounds of the object's String in the preferred Font
settings. When we want to apply an alignment to a Message
object, I create a Box
object which contains the x,y position and the width and height. By calling Allign.applyAllignment(params)
the calculations will be applied to align the Message
object in the Box
, and the Message
's dx, dy (drawing x, y positions) will be set.
Till here it works fine.
Now I create a MessageList
object and add some (Message
objects to it. When applying an alignment to this, it will run through the Message
objects it contains, and will call the obtainFontDimensions()
on them. The height of these Strings will be summed, and results into a total height (int listHeight
) of the Strings together. To get the drawing position of each Message
object, we take the y-position of the Box
where we want to align in. First we remove the listHeight
of the Box
's y position:
Now we got the offset of the first String. When the bottom alignment is being applied, it just adds the height of the Box
to the offset. Finally, the offset is set for the next Message
object by adding the current Message
object height to te offset. Time for the next iteration, till the ArrayList has been fully calculated. This should result in the following:
When applying an alignment to a MessageList
object, some Strings touch eachother perfectly (see circle B on image), and some keep some pixels more distance then others (see circle A1, A2 on image). And next to that, there remains an unexpected padding on the bottom (see circle C on image).
(only important parts)
public class Window()
{
public void draw(Graphics2D g2d)
{
Box contentBox = new Box(100, 100, 300, 300);
Message loadTitle = new Message("This is a testing TITLE", Colors.ORANGE, Fonts.LOADING_TITLE, false);
Message loadDescription = new Message(loadString, Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadTip = new Message("This is a random TIP!", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadRelease = new Message("Planned game release 2939!", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadSingle = new Message("THIS IS A SINGLE MESSAGE! 2o15", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
MessageList list = new MessageList();
list.add(loadTitle);
list.add(loadDescription);
list.add(loadTip);
list.add(loadRelease);
list.add(loadSingle);
Allign.applyAllignment(g2d, Allignment.BOTTOM_RIGHT, list, loadBox);
loadBox.testDraw(g2d);
loadTitle.draw(g2d);
loadDescription.draw(g2d);
loadTip.draw(g2d);
loadRelease.draw(g2d);
loadSingle.draw(g2d);
}
}
public class Message
{
private String text;
private Color color;
private Font font;
private int dx, dy;
private int width, height;
private Rectancle2D vb; // temp
public Message(String text, int x, int y, Color color, Font font)
{
// set text, color, font, x, y..
}
public Rectangle2D obtainFontDimension(Graphics2D g2d)
{
if(font == null){ font = Fonts.DEFAULT; }
g2d.setFont(font);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = g2d.getFont().createGlyphVector(frc, text);
Rectangle2D vb = gv.getPixelBounds(null, 0, 0);
this.width = (int)vb.getWidth();
this.height = (int)vb.getHeight();
this.gv = gv; // TEMP for bound drawing
return vb;
}
public void draw(Graphics2D g2d)
{
g2d.setFont(font);
g2d.setColor(color);
g2d.drawString(text, dx, dy);
// TEMP draw bounds
g2d.setColor(new Color(0, 0, 0, 100));
g2d.draw(gv.getPixelBounds(null, dx, dy));
}
}
public class Allign
{
public static enum Allignment
{
BOTTOM_RIGHT
//, etc
}
public static void applyAllignment(Graphics2D g2d, Allignment allignment, Object object, Box box)
{
Point position = null;
Point dimension = null;
if(obj instanceof Message){ // Single Message object }
else if(obj instanceof Message)
{
MessageList messageList = (MessageList) obj;
int listHeight = 0;
for(Message message : messageList.getList())
{
listHeight += message.obtainFontDimension(g2d).getHeight();
}
position = new Point(box.x, box.y-listHeight); // offset Y
for(Message message : messageList.getList())
{
message.setDrawPosition(allign(allignment, position, new Dimension(message.getWidth(), 0), box, true));
position.y += message.getHeight();
}
}
}
private static Point allign(Allignment allignment, Point position, Dimension dimension, Box box, boolean verticalAllign)
{
switch(allignment)
{
case BOTTOM_RIGHT:
position = allignRight(position, dimension, box);
if(!verticalAllign) break;
position = allignBottom(position, dimension, box);
break;
// Rest
}
}
private static Point allignBottom(Point position, Dimension dimension, Box box)
{
return new Point(position.x, position.y+box.height-dimension.height);
}
}
Thanks David Perez and VGR for the inputs!
I have switched back to the Font metrics, grabbed the height of the string's bounds. However, this only uses the height of the baseline to the highest ascending point. For a proper bottom spacing (like on top) I added the descent integer aswell.
public int obtainFontDimension(Graphics2D g2d)
{
if(font == null){ font = Fonts.DEFAULT; }
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics(font);
Rectangle2D sb = fm.getStringBounds(text, g2d);
this.width = (int)sb.getWidth();
this.height = (int)sb.getHeight();
this.descent = (int)fm.getDescent(); // added
tempShape = new Rectangle(width, height+descent); // Temp for drawing bounds
return height;
}
Before the allignment begins, I first calculate the total height of all of the Strings in the MessageList. The total height is the height of the String including the descending height.
Then I need to get the offset height for each vertical allignment possibility, which I've added. (TOP, MIDDLE, BOTTOM)
After that, we allign every Message under eachother by adding the height and descent to the offset on every itteration, with vertical allignment disabled this time, or it will reallign every Message horizontally as a single Message object instead as a group of Messages, but it does allow it to allign vertically.
if (obj instanceof MessageList)
{
MessageList messageList = (MessageList) obj;
int listHeight = 0;
for(Message message : messageList.getList())
{
message.obtainFontDimension(g2d);
listHeight += message.getHeight()+message.getDescent();
}
position = new Point(box.x, box.y);
Dimension listDimension = new Dimension(0, listHeight);
if( allignment == Allignment.MIDDLE || allignment == Allignment.MIDDLE_LEFT
|| allignment == Allignment.ABSOLUTE_MIDDLE || allignment == Allignment.MIDDLE_RIGHT)
{
position = allign(Allignment.MIDDLE, position, listDimension, box, true);
}
else if(allignment == Allignment.BOTTOM || allignment == Allignment.BOTTOM_LEFT
|| allignment == Allignment.BOTTOM_CENTER || allignment == Allignment.BOTTOM_RIGHT)
{
position = allign(Allignment.BOTTOM, position, listDimension, box, true);
}
else
{
position = allign(Allignment.TOP, position, listDimension, box, true);
}
for(Message message : messageList.getList())
{
position.y += message.getHeight(); // prepare the offset
message.setDrawPosition(allign(allignment, position, new Dimension(message.getWidth(), 0), box, true));
position.y += message.getDescent(); // add descending offset for next itteration
}
}
To draw the Messages with the new bounds:
public void draw(Graphics2D g2d)
{
g2d.setFont(font);
g2d.setColor(color);
g2d.drawString(text, dx, dy);
// Drawing bounds for testing
g2d.setColor(new Color(0, 0, 0, 100));
shape.setLocation(dx, dy-height);
g2d.draw(tempShape);
}
Final result:
Thanks again!