Hi
This is what I've got so far with my my baseline implementation - how
does it look?
Basic architecture
- VerticalAlignment gets a new constant BASELINE
- ConstrainedVisual has a new method
public int getBaseline(int width);
I had to put the method at a higher level than Component because I
also need to call it on
Skin and Renderer instances.
- ComponentSkin has a static flag for debugging support for baselines,
which draws red lines on the components to make it easier to see where
the problems are.
To turn it on, pass
-Dpivot.wtk.debugBaseline=true
on the command line
TODO
- I'm unsure how best to calculate a baseline for TextAreaSkin
- TablePaneSkin still needs a getBaseline() implementation
- FlowPaneSkin - It is possible to end up with illegal combinations e.g.
verticalAlignment==BASELINE && orientation==VERTICAL. But how to flag
these kinds of errors?
Regards, Noel.
Index: wtk/src/pivot/wtk/Component.java
===================================================================
--- wtk/src/pivot/wtk/Component.java (revision 782639)
+++ wtk/src/pivot/wtk/Component.java (working copy)
@@ -1164,6 +1164,16 @@
}
/**
+ * Returns the component's baseline for a given width.
+ *
+ * @return The component's baseline relative to the origin of the parent
+ * container. -1 indicates that no baseline exists.
+ */
+ public int getBaseline(int width) {
+ return skin.getBaseline(width);
+ }
+
+ /**
* Returns the component's bounding area.
*
* @return
Index: wtk/src/pivot/wtk/ConstrainedVisual.java
===================================================================
--- wtk/src/pivot/wtk/ConstrainedVisual.java (revision 782639)
+++ wtk/src/pivot/wtk/ConstrainedVisual.java (working copy)
@@ -49,7 +49,15 @@
* for no constraint.
*/
public int getPreferredHeight(int width);
-
+
+ /**
+ * Returns the baseline for a given width.
+ *
+ * @return The baseline relative to the origin of the parent
+ * container. -1 indicates that no baseline exists.
+ */
+ public int getBaseline(int width);
+
/**
* Returns the visual's unconstrained preferred size.
*/
Index: wtk/src/pivot/wtk/VerticalAlignment.java
===================================================================
--- wtk/src/pivot/wtk/VerticalAlignment.java (revision 782639)
+++ wtk/src/pivot/wtk/VerticalAlignment.java (working copy)
@@ -27,6 +27,11 @@
CENTER,
/**
+ * align the components along their baselines
+ */
+ BASELINE,
+
+ /**
* Stretch the contents to fill the available space.
*/
JUSTIFY;
Index: wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java
===================================================================
--- wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java (revision
782639)
+++ wtk/src/pivot/wtk/content/TableViewMultiCellRenderer.java (working copy)
@@ -286,6 +286,20 @@
return preferredHeight;
}
+
+ public int getBaseline(int width) {
+ // Our baseline is the maximum of all our possible renderers'
+ // baseline
+ int baseline = defaultRenderer.getBaseline(width);
+
+ for (Class<?> key : cellRenderers) {
+ TableView.CellRenderer renderer = cellRenderers.get(key);
+ baseline = Math.max(baseline,
+ renderer.getBaseline(width));
+ }
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
Index: wtk/src/pivot/wtk/skin/CardPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/CardPaneSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/CardPaneSkin.java (working copy)
@@ -244,6 +244,27 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ int baseline = -1;
+
+ CardPane cardPane = (CardPane)getComponent();
+
+ if (sizeToSelection) {
+ Component selectedCard = cardPane.getSelectedCard();
+
+ if (selectedCard != null) {
+ baseline = selectedCard.getBaseline(width);
+ }
+ } else {
+ for (Component card : cardPane) {
+ baseline = Math.max(baseline, card.getBaseline(width));
+ }
+ }
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
Dimensions preferredSize;
Index: wtk/src/pivot/wtk/skin/ComponentSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/ComponentSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/ComponentSkin.java (working copy)
@@ -16,7 +16,11 @@
*/
package pivot.wtk.skin;
+import java.awt.Color;
+import java.awt.Graphics2D;
+
import pivot.wtk.ApplicationContext;
+import pivot.wtk.Bounds;
import pivot.wtk.Component;
import pivot.wtk.ComponentKeyListener;
import pivot.wtk.ComponentLayoutListener;
@@ -29,9 +33,10 @@
import pivot.wtk.Cursor;
import pivot.wtk.Dimensions;
import pivot.wtk.Direction;
+import pivot.wtk.GraphicsUtilities;
import pivot.wtk.Keyboard;
import pivot.wtk.Mouse;
-import pivot.wtk.Bounds;
+import pivot.wtk.Orientation;
import pivot.wtk.Point;
import pivot.wtk.Skin;
import pivot.wtk.Tooltip;
@@ -73,6 +78,16 @@
public static final int SHOW_TOOLTIP_TIMEOUT = 1000;
+ /** if true, draw red lines over components to indicate where the
baselines are */
+ protected static boolean debugBaseline = false;
+ static {
+ try {
+ debugBaseline =
Boolean.parseBoolean(System.getProperty("pivot.wtk.debugBaseline"));
+ } catch (Exception ex) {
+ // ignore exception when running in applet
+ }
+ }
+
public int getWidth() {
return width;
}
@@ -81,6 +96,11 @@
return height;
}
+
+ public int getBaseline(int width) {
+ return -1;
+ }
+
public void setSize(int width, int height) {
this.width = width;
this.height = height;
@@ -289,4 +309,13 @@
component.repaint(x, y, width, height);
}
}
+
+ protected final void drawBaselineDebug(Graphics2D graphics) {
+ int width = getWidth();
+ int baseline = getBaseline(width);
+ if (baseline != -1) {
+ graphics.setPaint(Color.RED);
+ GraphicsUtilities.drawLine(graphics, 0, baseline, width,
Orientation.HORIZONTAL);
+ }
+ }
}
Index: wtk/src/pivot/wtk/skin/FlowPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/FlowPaneSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/FlowPaneSkin.java (working copy)
@@ -16,6 +16,8 @@
*/
package pivot.wtk.skin;
+import java.awt.Graphics2D;
+
import pivot.collections.Dictionary;
import pivot.wtk.Component;
import pivot.wtk.Dimensions;
@@ -180,9 +182,11 @@
// Preferred height is the maximum preferred height of all
// components, plus padding
int maxComponentHeight = 0;
+ int maxComponentBaseline = 0;
+ int maxComponentBelowBaseline = 0;
// Determine the fixed and total preferred widths, if necessary
- int totalSpacing = 0;
+ int totalSpacingWidth = 0;
int totalPreferredWidth = 0;
if (horizontalAlignment == HorizontalAlignment.JUSTIFY
@@ -199,7 +203,7 @@
}
if (displayableComponentCount > 1) {
- totalSpacing = spacing * (displayableComponentCount - 1);
+ totalSpacingWidth = spacing * (displayableComponentCount -
1);
}
}
@@ -213,22 +217,51 @@
&& width != -1) {
int preferredWidth = component.getPreferredWidth(-1);
- if (width > totalSpacing
- && preferredWidth > totalSpacing) {
+ if (width > totalSpacingWidth
+ && preferredWidth > totalSpacingWidth) {
double widthScale = (double)preferredWidth
/ (double)totalPreferredWidth;
componentWidth = (int)Math.round((double)(width
- - totalSpacing) * widthScale);
+ - totalSpacingWidth) * widthScale);
}
}
+ int componentPreferredHeight = component
+ .getPreferredHeight(componentWidth);
maxComponentHeight = Math.max(maxComponentHeight,
- component.getPreferredHeight(componentWidth));
+ componentPreferredHeight);
+
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ int componentBaseline = component
+ .getBaseline(componentWidth);
+ if (componentBaseline != -1) {
+ maxComponentBaseline = Math.max(
+ maxComponentBaseline, componentBaseline);
+ int componentHeightBelowBaseline =
componentPreferredHeight
+ - componentBaseline;
+ maxComponentBelowBaseline = Math.max(
+ maxComponentBelowBaseline,
+ componentHeightBelowBaseline);
+ } else {
+ // if no baseline exists, treat the bottom of the
+ // component as baseline
+ maxComponentBaseline = Math.max(
+ maxComponentBaseline,
+ componentPreferredHeight);
+ maxComponentBelowBaseline = Math.max(
+ maxComponentBelowBaseline, 0);
+ }
+ }
}
}
- preferredHeight += maxComponentHeight;
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ preferredHeight += maxComponentBaseline;
+ preferredHeight += maxComponentBelowBaseline;
+ } else {
+ preferredHeight += maxComponentHeight;
+ }
}
// Include top and bottom padding values
@@ -248,6 +281,8 @@
// Preferred width is the sum of the preferred widths of all
// components, plus spacing and padding
int displayableComponentCount = 0;
+ int maxBaseline = 0;
+ int maxBelowBaseline = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
@@ -255,10 +290,35 @@
if (component.isDisplayable()) {
Dimensions preferredSize =
component.getPreferredSize();
preferredWidth += preferredSize.width;
- preferredHeight = Math.max(preferredSize.height,
preferredHeight);
- displayableComponentCount++;
+
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ int componentBaseline = component
+ .getBaseline(preferredSize.width);
+ if (componentBaseline != -1) {
+ maxBaseline = Math.max(maxBaseline, maxBaseline);
+ int componentHeightBelowBaseline =
preferredSize.height
+ - componentBaseline;
+ maxBelowBaseline = Math.max(maxBelowBaseline,
+ componentHeightBelowBaseline);
+ } else {
+ // if no baseline exists, treat the bottom of the
+ // component as baseline
+ maxBaseline = Math.max(maxBaseline,
+ preferredSize.height);
+ maxBelowBaseline = Math.max(maxBelowBaseline, 0);
+ }
+ } else {
+ preferredHeight = Math.max(preferredSize.height,
+ preferredHeight);
}
+
+ displayableComponentCount++;
}
+ }
+
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ preferredHeight = maxBaseline + maxBelowBaseline;
+ }
// Include spacing
if (displayableComponentCount > 1) {
@@ -309,7 +369,7 @@
Orientation orientation = flowPane.getOrientation();
if (orientation == Orientation.HORIZONTAL) {
- int preferredWidth = getPreferredWidth(-1);
+ int preferredWidth = getPreferredWidth(height);
// Determine the fixed width (used in scaling components
// when justified horizontally)
@@ -348,7 +408,13 @@
}
componentX += padding.left;
-
+
+ int baseline = 0;
+ // only compute baseline if we need it
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ baseline = getBaseline(width);
+ }
+
// Lay out the components
for (int i = 0; i < n; i++) {
Component component = flowPane.get(i);
@@ -389,8 +455,17 @@
componentHeight = preferredComponentSize.height;
}
}
-
+
+ // Determine the component y-coordinate
switch (verticalAlignment) {
+ case BASELINE: {
+ // adjust component Y so that all the baselines
line up
+ int componentBaseline = component
+ .getBaseline(componentWidth);
+ componentY = padding.top + (baseline -
componentBaseline);
+ break;
+ }
+
case TOP: {
componentY = padding.top;
break;
@@ -423,7 +498,7 @@
}
}
} else {
- int preferredHeight = getPreferredHeight(-1);
+ int preferredHeight = getPreferredHeight(width);
// Determine the fixed height (used in scaling components
// when justified vertically)
@@ -539,6 +614,103 @@
}
}
+ @Override
+ public int getBaseline(int width) {
+ int baseline = -1;
+
+ // Include padding in constraint
+ if (width != -1) {
+ width = Math.max(width - (padding.left + padding.right), 0);
+ }
+
+ FlowPane flowPane = (FlowPane) getComponent();
+ int n = flowPane.getLength();
+
+ Orientation orientation = flowPane.getOrientation();
+ if (orientation == Orientation.VERTICAL) {
+ // baseline is the baseline of the first component
+ for (int i = 0; i < n; i++) {
+ Component component = flowPane.get(i);
+
+ if (component.isDisplayable()) {
+ baseline = component.getBaseline(width);
+ break;
+ }
+ }
+ } else {
+ // baseline is the maximum baseline of all
+ // components, plus padding
+
+ // Determine the fixed and total preferred widths, if necessary
+ int totalSpacingWidth = 0;
+ int totalPreferredWidth = 0;
+
+ if (horizontalAlignment == HorizontalAlignment.JUSTIFY
+ && width != -1) {
+ int displayableComponentCount = 0;
+
+ for (int i = 0; i < n; i++) {
+ Component component = flowPane.get(i);
+
+ if (component.isDisplayable()) {
+ totalPreferredWidth += component.getPreferredWidth(-1);
+ displayableComponentCount++;
+ }
+ }
+
+ if (displayableComponentCount > 1) {
+ totalSpacingWidth = spacing
+ * (displayableComponentCount - 1);
+ }
+ }
+
+ for (int i = 0; i < n; i++) {
+ Component component = flowPane.get(i);
+
+ if (component.isDisplayable()) {
+ int componentWidth = -1;
+
+ if (horizontalAlignment == HorizontalAlignment.JUSTIFY
+ && width != -1) {
+ int preferredWidth = component.getPreferredWidth(-1);
+
+ if (width > totalSpacingWidth
+ && preferredWidth > totalSpacingWidth) {
+ double widthScale = (double) preferredWidth
+ / (double) totalPreferredWidth;
+
+ componentWidth = (int) Math
+ .round((double) (width - totalSpacingWidth)
+ * widthScale);
+ }
+ }
+
+ int componentBaseline = component
+ .getBaseline(componentWidth);
+ if (componentBaseline != -1) {
+ baseline = Math.max(baseline, componentBaseline);
+ }
+ }
+ }
+
+ }
+
+ // Include top and bottom padding values
+ if (baseline != -1) {
+ baseline += padding.top;
+ }
+
+ return baseline;
+ }
+
+ @Override
+ public void paint(Graphics2D graphics) {
+ super.paint(graphics);
+ if (debugBaseline) {
+ drawBaselineDebug(graphics);
+ }
+ }
+
public HorizontalAlignment getHorizontalAlignment() {
return horizontalAlignment;
}
@@ -564,6 +736,11 @@
return verticalAlignment;
}
+ /**
+ * @TODO It is possible to end up with impossible combinations e.g.
+ * verticalAlignment==BASELINE && orientation==VERTICAL. But how to
+ * flag these kinds of errors?
+ */
public void setVerticalAlignment(VerticalAlignment verticalAlignment) {
if (verticalAlignment == null) {
throw new IllegalArgumentException("verticalAlignment is null.");
Index: wtk/src/pivot/wtk/skin/ImageViewSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/ImageViewSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/ImageViewSkin.java (working copy)
@@ -292,6 +292,12 @@
if (verticalAlignment == null) {
throw new IllegalArgumentException("verticalAlignment is null.");
}
+ if (verticalAlignment != VerticalAlignment.JUSTIFY
+ && verticalAlignment != VerticalAlignment.CENTER
+ && verticalAlignment != VerticalAlignment.BOTTOM
+ && verticalAlignment != VerticalAlignment.TOP) {
+ throw new IllegalStateException(verticalAlignment + " is not
supported");
+ }
this.verticalAlignment = verticalAlignment;
layout();
Index: wtk/src/pivot/wtk/skin/LabelSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/LabelSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/LabelSkin.java (working copy)
@@ -182,6 +182,52 @@
public void layout() {
// No-op
}
+
+ @Override
+ public int getBaseline(int width) {
+ Label label = (Label)getComponent();
+
+ /* calculate the baseline of the text */
+ int baseline = -1;
+
+ String text = label.getText();
+ if (text == null) {
+ text = "";
+ }
+
+ if (wrapText
+ && text.length() > 0) {
+ int contentWidth = label.getWidth() - (padding.left + padding.right);
+
+ AttributedString attributedText = new AttributedString(text);
+ attributedText.addAttribute(TextAttribute.FONT, font);
+
+ AttributedCharacterIterator aci = attributedText.getIterator();
+ LineBreakMeasurer lbm = new LineBreakMeasurer(aci,
fontRenderContext);
+
+ if (lbm.getPosition() < aci.getEndIndex()) {
+ LineMetrics lm = font.getLineMetrics(text, fontRenderContext);
+ baseline = (int)Math.ceil(lm.getAscent()-2);
+ } else {
+ // for multi-line labels, treat the baseline as being the
baseline of the first line of text
+ int offset = lbm.nextOffset(contentWidth);
+
+ LineMetrics lm = font.getLineMetrics(aci,
+ lbm.getPosition(), offset, fontRenderContext);
+
+ baseline = (int) Math.ceil(lm.getAscent()-2);
+ }
+ } else {
+ LineMetrics lm = font.getLineMetrics(text, fontRenderContext);
+ baseline = (int)Math.ceil(lm.getAscent()-2);
+ }
+
+ if (baseline!=-1) {
+ baseline += padding.top;
+ }
+
+ return baseline;
+ }
public void paint(Graphics2D graphics) {
int width = getWidth();
@@ -190,6 +236,10 @@
Label label = (Label)getComponent();
String text = label.getText();
+ if (debugBaseline) {
+ drawBaselineDebug(graphics);
+ }
+
if (text != null
&& text.length() > 0) {
if (fontRenderContext.isAntiAliased()) {
@@ -445,8 +495,10 @@
throw new IllegalArgumentException("verticalAlignment is null.");
}
- if (verticalAlignment == VerticalAlignment.JUSTIFY) {
- throw new IllegalArgumentException(VerticalAlignment.JUSTIFY + "
is not supported");
+ if (verticalAlignment != VerticalAlignment.TOP
+ && verticalAlignment != VerticalAlignment.BOTTOM
+ && verticalAlignment != VerticalAlignment.CENTER) {
+ throw new IllegalArgumentException(verticalAlignment + " is not
supported");
}
this.verticalAlignment = verticalAlignment;
Index: wtk/src/pivot/wtk/skin/StackPaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/StackPaneSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/StackPaneSkin.java (working copy)
@@ -50,6 +50,19 @@
return preferredHeight;
}
+ @Override
+ public int getBaseline(int width) {
+ int baseline = 0;
+ StackPane stackPane = (StackPane)getComponent();
+
+ for (Component component : stackPane) {
+ baseline = Math.max(baseline,
+ component.getBaseline(width));
+ }
+
+ return baseline;
+ }
+
public Dimensions getPreferredSize() {
int preferredWidth = 0;
int preferredHeight = 0;
Index: wtk/src/pivot/wtk/skin/TablePaneSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/TablePaneSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/TablePaneSkin.java (working copy)
@@ -38,6 +38,8 @@
/**
* Table pane skin.
*
+ * TODO (NoelG) implement baseline alignment for rows
+ *
* @author tvolkert
*/
public class TablePaneSkin extends ContainerSkin implements TablePane.Skin,
@@ -414,7 +416,7 @@
return preferredHeight;
}
-
+
@Override
public Dimensions getPreferredSize() {
// TODO Optimize by performing calculations here
Index: wtk/src/pivot/wtk/skin/TextAreaSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/TextAreaSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/TextAreaSkin.java (working copy)
@@ -72,6 +72,8 @@
/**
* Text area skin.
*
+ * TODO (NoelG) override getBaseline() and calculate a real baseline
+ *
* @author gbrown
*/
public class TextAreaSkin extends ComponentSkin implements TextArea.Skin,
Index: wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraCheckboxSkin.java (working copy)
@@ -114,6 +114,27 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ Checkbox checkbox = (Checkbox)getComponent();
+ Button.DataRenderer dataRenderer = checkbox.getDataRenderer();
+
+ int baseline = -1;
+
+ Object buttonData = checkbox.getButtonData();
+ if (buttonData != null) {
+ if (width != -1) {
+ width = Math.max(width - (CHECKBOX_SIZE + spacing), 0);
+ }
+
+ dataRenderer.render(checkbox.getButtonData(), checkbox, false);
+
+ baseline = dataRenderer.getBaseline(width);
+ }
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
Checkbox checkbox = (Checkbox)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraFormSkin.java (working copy)
@@ -30,6 +30,7 @@
import pivot.wtk.MessageType;
import pivot.wtk.Separator;
import pivot.wtk.Theme;
+import pivot.wtk.VerticalAlignment;
import pivot.wtk.media.Image;
import pivot.wtk.skin.ContainerSkin;
@@ -46,6 +47,7 @@
private boolean rightAlignLabels = true;
private HorizontalAlignment fieldAlignment = HorizontalAlignment.LEFT;
+ private VerticalAlignment verticalAlignment = VerticalAlignment.BASELINE;
private int horizontalSpacing = 6;
private int verticalSpacing = 6;
private int flagImageOffset = 4;
@@ -176,8 +178,26 @@
if (field.isDisplayable()) {
Label label = labels.get(sectionIndex).get(fieldIndex);
- int preferredRowHeight =
Math.max(label.getPreferredHeight(-1),
- Math.max(field.getPreferredHeight(fieldWidth),
FLAG_IMAGE_SIZE));
+ int preferredRowHeight = 0;
+ if (verticalAlignment==VerticalAlignment.BASELINE) {
+ int labelBaseLine = label.getBaseline(-1);
+ int fieldBaseLine = field.getBaseline(-1);
+ if (labelBaseLine!=-1 && fieldBaseLine!=-1) {
+ int fieldPrefferedHeight =
field.getPreferredHeight(fieldWidth);
+ int labelPreferredHeight =
label.getPreferredHeight(-1);
+ int baseline = Math.max(labelBaseLine,
fieldBaseLine);
+ int belowBaseline = Math.max(fieldPrefferedHeight
- fieldBaseLine, labelPreferredHeight - labelBaseLine);
+ preferredRowHeight = Math.max(preferredRowHeight,
baseline + belowBaseline);
+ } else {
+ // if they don't both have baselines, default to
non-baseline behaviour
+ preferredRowHeight = Math.max(preferredRowHeight,
field.getPreferredHeight(fieldWidth));
+ preferredRowHeight = Math.max(preferredRowHeight,
label.getPreferredHeight(-1));
+ }
+ } else {
+ preferredRowHeight = Math.max(preferredRowHeight,
field.getPreferredHeight(fieldWidth));
+ preferredRowHeight = Math.max(preferredRowHeight,
label.getPreferredHeight(-1));
+ }
+ preferredRowHeight = Math.max(preferredRowHeight,
FLAG_IMAGE_SIZE);
preferredHeight += preferredRowHeight;
if (fieldIndex > 0) {
@@ -243,7 +263,7 @@
separator.setLocation(0, rowY);
rowY += separator.getHeight();
}
-
+
for (int fieldIndex = 0, fieldCount = section.getLength();
fieldIndex < fieldCount; fieldIndex++) {
Component field = section.get(fieldIndex);
@@ -274,9 +294,34 @@
int rowHeight = Math.max(label.getHeight(),
Math.max(field.getHeight(), FLAG_IMAGE_SIZE));
+ int fieldY;
+ int labelY;
+ int flagImageY;
+ if (verticalAlignment == VerticalAlignment.BASELINE) {
+ int labelBaseLine =
label.getBaseline(label.getWidth());
+ int fieldBaseLine = field.getBaseline(fieldSize.width);
+ if (labelBaseLine!=-1 && fieldBaseLine!=-1) {
+ int baseline = Math.max(labelBaseLine,
fieldBaseLine);
+ labelY = rowY + (baseline - labelBaseLine);
+ fieldY = rowY + (baseline - fieldBaseLine);
+ // make the bottom of the flag line up with
baseline
+ flagImageY = rowY + (flagImageView.getHeight() -
baseline);
+ } else {
+ // if they don't both have baselines, default to
non-baseline behaviour
+ fieldY = rowY;
+ labelY = rowY;
+ flagImageY = fieldY + (rowHeight -
flagImageView.getHeight()) / 2;
+ }
+ } else {
+ fieldY = rowY;
+ labelY = rowY;
+ flagImageY = fieldY + (rowHeight -
flagImageView.getHeight()) / 2;
+ }
+
+
// Set the row component locations
int labelX = rightAlignLabels ? maximumLabelWidth -
label.getWidth() : 0;
- label.setLocation(labelX, rowY);
+ label.setLocation(labelX, labelY);
int fieldX = 0;
switch(fieldAlignment) {
@@ -301,9 +346,9 @@
}
}
- field.setLocation(fieldX, rowY);
+ field.setLocation(fieldX, fieldY);
flagImageView.setLocation(fieldX + field.getWidth() +
flagImageOffset,
- rowY + (rowHeight - flagImageView.getHeight()) / 2);
+ flagImageY);
// Update the row y-coordinate
rowY += rowHeight + verticalSpacing;
Index: wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java (revision
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraLinkButtonSkin.java (working copy)
@@ -61,6 +61,16 @@
return dataRenderer.getPreferredHeight(width);
}
+
+ @Override
+ public int getBaseline(int width) {
+ LinkButton linkButton = (LinkButton)getComponent();
+
+ Button.DataRenderer dataRenderer = linkButton.getDataRenderer();
+ dataRenderer.render(linkButton.getButtonData(), linkButton, false);
+
+ return dataRenderer.getBaseline(width);
+ }
public Dimensions getPreferredSize() {
LinkButton linkButton = (LinkButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java (revision
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraListButtonSkin.java (working copy)
@@ -211,6 +211,19 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ ListButton listButton = (ListButton)getComponent();
+ Button.DataRenderer dataRenderer = listButton.getDataRenderer();
+
+ dataRenderer.render(listButton.getButtonData(), listButton, false);
+
+ int baseline = dataRenderer.getBaseline(width)
+ + padding.top + 1;
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
// TODO Optimize by performing calcuations locally
Index: wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraListViewSkin.java (working copy)
@@ -139,6 +139,25 @@
return preferredHeight;
}
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public int getBaseline(int width) {
+ int baseline = -1;
+
+ ListView listView = (ListView)getComponent();
+ List<Object> listData = (List<Object>)listView.getListData();
+
+ ListView.ItemRenderer renderer = listView.getItemRenderer();
+
+ if (listData.getLength()>0) {
+ Object item = listData.get(0);
+ renderer.render(item, listView, false, false, false, false);
+ baseline = renderer.getBaseline(width);
+ }
+
+ return baseline;
+ }
public void layout() {
// No-op
Index: wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java (revision
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraPushButtonSkin.java (working copy)
@@ -139,6 +139,37 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ int baseline = -1;
+
+ PushButton pushButton = (PushButton)getComponent();
+ Button.DataRenderer dataRenderer = pushButton.getDataRenderer();
+
+ dataRenderer.render(pushButton.getButtonData(), pushButton, false);
+
+ // Include padding in constraint
+ int contentWidth = width;
+ if (contentWidth != -1) {
+ contentWidth = Math.max(contentWidth - (padding.left +
padding.right + 2), 0);
+ }
+
+ int preferredHeight1 = dataRenderer.getPreferredHeight(contentWidth)
+ + padding.top + padding.bottom + 2;
+ baseline = dataRenderer.getBaseline(contentWidth)
+ + padding.top + 1;
+
+ // Adjust for preferred aspect ratio
+ if (!Float.isNaN(preferredAspectRatio)
+ && preferredAspectRatio >= 1
+ && (float)width / (float)preferredHeight1 < preferredAspectRatio) {
+ int preferredHeight2 = (int)((float)width / preferredAspectRatio);
+ baseline = (int) (preferredHeight1 / (float)preferredHeight2 *
baseline);
+ }
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
PushButton pushButton = (PushButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java (revision
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraRadioButtonSkin.java (working copy)
@@ -98,6 +98,22 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ RadioButton radioButton = (RadioButton)getComponent();
+ Button.DataRenderer dataRenderer = radioButton.getDataRenderer();
+
+ dataRenderer.render(radioButton.getButtonData(), radioButton, false);
+
+ if (width != -1) {
+ width = Math.max(width - (BUTTON_DIAMETER + spacing), 0);
+ }
+
+ int baseline = dataRenderer.getBaseline(width);
+
+ return baseline;
+ }
public Dimensions getPreferredSize() {
RadioButton radioButton = (RadioButton)getComponent();
Index: wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraSheetSkin.java (working copy)
@@ -161,6 +161,29 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ int baseline = -1;
+
+ Sheet sheet = (Sheet)getComponent();
+ Component content = sheet.getContent();
+
+ if (content != null
+ && content.isDisplayable()) {
+ if (width != -1) {
+ width = Math.max(width - (padding.left + padding.right + 2),
0);
+ }
+
+ baseline = content.getPreferredHeight(width);
+ }
+
+ if (baseline != -1) {
+ baseline += padding.top + 1;
+ }
+
+ return baseline;
+ }
@Override
public Dimensions getPreferredSize() {
Index: wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java (revision 782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraSpinnerSkin.java (working copy)
@@ -584,6 +584,21 @@
return preferredHeight;
}
+
+ @Override
+ public int getBaseline(int width) {
+ if (width >= 0) {
+ // Subtract the button and border width from width constraint
+ Dimensions upButtonPreferredSize = upButton.getPreferredSize();
+ Dimensions downButtonPreferredSize = downButton.getPreferredSize();
+ int buttonWidth = Math.max(upButtonPreferredSize.width,
+ downButtonPreferredSize.width);
+
+ width = Math.max(width - buttonWidth - 2, 0);
+ }
+ int baseline = spinnerContent.getBaseline(width) + 1;
+ return baseline;
+ }
public void layout() {
int width = getWidth();
Index: wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java
===================================================================
--- wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java (revision
782639)
+++ wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java (working copy)
@@ -27,7 +27,6 @@
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
-// import java.text.AttributedCharacterIterator;
import pivot.collections.Dictionary;
import pivot.wtk.ApplicationContext;
@@ -310,13 +309,14 @@
super.uninstall();
}
+ // TODO Localize?
+ private static final String testString =
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+
public int getPreferredWidth(int height) {
TextInput textInput = (TextInput)getComponent();
int textSize = textInput.getTextSize();
- // TODO Localize?
// TODO Recalculate only when font changes
- String testString =
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
Rectangle2D testStringBounds = font.getStringBounds(testString,
fontRenderContext);
int averageCharWidth = (int)Math.round((testStringBounds.getWidth() /
testString.length()));
@@ -340,6 +340,21 @@
// No-op
}
+ @Override
+ public int getBaseline(int width) {
+ /* calculate the baseline of the text */
+ int baseline = -1;
+
+ LineMetrics lm = font.getLineMetrics(testString, fontRenderContext);
+ baseline = (int)Math.ceil(lm.getAscent()-2);
+
+ if (baseline!=-1) {
+ baseline += padding.top + 1;
+ }
+
+ return baseline;
+ }
+
public void paint(Graphics2D graphics) {
TextInput textInput = (TextInput)getComponent();
@@ -380,6 +395,10 @@
graphics.setPaint(borderColor);
GraphicsUtilities.drawRect(graphics, 0, 0, width, height);
+ if (debugBaseline) {
+ drawBaselineDebug(graphics);
+ }
+
// Paint the content
String text = getText();