diff options
author | Tor Norbye <tnorbye@google.com> | 2011-01-26 19:33:37 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2011-01-27 16:12:19 -0800 |
commit | 4165cd94690a68be74232fa98ef40f9699fa2072 (patch) | |
tree | 8b899da68ba5570dab9ac28623519e565c68a7b3 | |
parent | c14e1be26b4fd9ca4f1f210df3ea9b1c083f5c1e (diff) | |
download | sdk-4165cd94690a68be74232fa98ef40f9699fa2072.zip sdk-4165cd94690a68be74232fa98ef40f9699fa2072.tar.gz sdk-4165cd94690a68be74232fa98ef40f9699fa2072.tar.bz2 |
Various palette improvements
1. Look up the theme background and foreground colors using theme
resources, if possible. These theme resources are then used to set
the foreground and background colors on the labels used as a
fallback for non-previewable widgets. In other words, when you're
looking at a dark theme, the layouts such as LinearLayout is now
shown using a white label on a dark background, making the palette
view more consistent visually.
2. Configure scrollbar increments properly such that scrolling the
views with scrollwheels or mouse gestures works better.
3. Fix a bug in the way preview images were copied out of the rendered
image; the root coordinates were not taken into account, which
matters for themes like Theme.Dialog.
Change-Id: I4832166a0560d33fe4e4dd8079c82a180e07e897
6 files changed, 305 insertions, 31 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java index 4a99dab..0c9c0fc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java @@ -38,6 +38,7 @@ import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.ScrollBar; import java.util.ArrayList; import java.util.List; @@ -245,6 +246,9 @@ public abstract class AccordionControl extends Composite { // Turn off border? final ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL); + ScrollBar verticalBar = scrolledComposite.getVerticalBar(); + verticalBar.setIncrement(20); + verticalBar.setPageIncrement(100); // Do we need the scrolled composite or can we just look at the next // wizard in the hierarchy? @@ -267,6 +271,8 @@ public abstract class AccordionControl extends Composite { if (content != null && r != null) { Point minSize = content.computeSize(r.width, SWT.DEFAULT); scrolledComposite.setMinSize(minSize); + ScrollBar vBar = scrolledComposite.getVerticalBar(); + vBar.setPageIncrement(r.height); } } }); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 8e4b12f..397aac9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -1700,6 +1700,21 @@ public class GraphicalEditorPart extends EditorPart } } + ResourceResolver createResolver() { + String theme = mConfigComposite.getTheme(); + boolean isProjectTheme = mConfigComposite.isProjectTheme(); + Map<String, Map<String, ResourceValue>> configuredProjectRes = + mConfigListener.getConfiguredProjectResources(); + + // Get the framework resources + Map<String, Map<String, ResourceValue>> frameworkResources = + mConfigListener.getConfiguredFrameworkResources(); + + return ResourceResolver.create( + configuredProjectRes, frameworkResources, + theme, isProjectTheme); + } + /** * Returns the resource name of this layout, NOT including the @layout/ prefix * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java index 1952462..94419b3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java @@ -373,4 +373,58 @@ public class ImageUtils { return sub; } + + /** + * Returns the color value represented by the given string value + * @param value the color value + * @return the color as an int + * @throw NumberFormatException if the conversion failed. + */ + public static int getColor(String value) { + // Copied from ResourceHelper in layoutlib + if (value != null) { + if (value.startsWith("#") == false) { //$NON-NLS-1$ + throw new NumberFormatException( + String.format("Color value '%s' must start with #", value)); + } + + value = value.substring(1); + + // make sure it's not longer than 32bit + if (value.length() > 8) { + throw new NumberFormatException(String.format( + "Color value '%s' is too long. Format is either" + + "#AARRGGBB, #RRGGBB, #RGB, or #ARGB", + value)); + } + + if (value.length() == 3) { // RGB format + char[] color = new char[8]; + color[0] = color[1] = 'F'; + color[2] = color[3] = value.charAt(0); + color[4] = color[5] = value.charAt(1); + color[6] = color[7] = value.charAt(2); + value = new String(color); + } else if (value.length() == 4) { // ARGB format + char[] color = new char[8]; + color[0] = color[1] = value.charAt(0); + color[2] = color[3] = value.charAt(1); + color[4] = color[5] = value.charAt(2); + color[6] = color[7] = value.charAt(3); + value = new String(color); + } else if (value.length() == 6) { + value = "FF" + value; //$NON-NLS-1$ + } + + // this is a RRGGBB or AARRGGBB value + + // Integer.parseInt will fail to parse strings like "ff191919", so we use + // a Long, but cast the result back into an int, since we know that we're only + // dealing with 32 bit values. + return (int)Long.parseLong(value, 16); + } + + throw new NumberFormatException(); + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java index 1aeda77..fbf7167 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java @@ -27,9 +27,9 @@ import com.android.ide.common.api.Rect; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.Params.RenderingMode; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.common.rendering.api.Params.RenderingMode; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -150,6 +150,7 @@ public class PaletteControl extends Composite { */ private GraphicalEditorPart mEditor; private Color mBackground; + private Color mForeground; /** The palette modes control various ways to visualize and lay out the views */ private static enum PaletteMode { @@ -281,6 +282,10 @@ public class PaletteControl extends Composite { mBackground.dispose(); mBackground = null; } + if (mForeground != null) { + mForeground.dispose(); + mForeground = null; + } super.dispose(); } @@ -337,11 +342,20 @@ public class PaletteControl extends Composite { } if (mPaletteMode.isPreview()) { - RGB background = mPreviewIconFactory.getBackgroundColor(); + if (mForeground != null) { + mForeground.dispose(); + mForeground = null; + } if (mBackground != null) { mBackground.dispose(); + mBackground = null; } + RGB background = mPreviewIconFactory.getBackgroundColor(); mBackground = new Color(getDisplay(), background); + RGB foreground = mPreviewIconFactory.getForegroundColor(); + if (foreground != null) { + mForeground = new Color(getDisplay(), foreground); + } } AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target); @@ -452,6 +466,10 @@ public class PaletteControl extends Composite { } else { // Just use an Icon+Text item for these for now item = new IconTextItem(parent, desc); + if (mForeground != null) { + item.setForeground(mForeground); + item.setBackground(mBackground); + } } break; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java index 38d947f..a93c81e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java @@ -13,14 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.eclipse.adt.AndroidConstants.DOT_PNG; +import static com.android.ide.eclipse.adt.AndroidConstants.DOT_XML; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.common.rendering.api.Params.RenderingMode; +import com.android.ide.common.resources.ResourceResolver; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -30,14 +36,18 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepos import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.sdklib.util.Pair; import org.eclipse.core.runtime.IPath; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.RGB; +import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; @@ -53,14 +63,19 @@ import java.util.Properties; import java.util.Set; import javax.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; /** * Factory which can provide preview icons for android views of a particular SDK and * editor's configuration chooser */ public class PreviewIconFactory { + private static final String TAG_ITEM = "item"; //$NON-NLS-1$ + private static final String ATTR_COLOR = "color"; //$NON-NLS-1$ private PaletteControl mPalette; private RGB mBackground; + private RGB mForeground; private File mImageDir; private static final String PREVIEW_INFO_FILE = "preview.properties"; //$NON-NLS-1$ @@ -77,6 +92,7 @@ public class PreviewIconFactory { public void reset() { mImageDir = null; mBackground = null; + mForeground = null; } /** @@ -143,8 +159,8 @@ public class PreviewIconFactory { } /** - * Renders ALL the widgets and then extracts image data for each view and saves it - * on disk + * Renders ALL the widgets and then extracts image data for each view and saves it on + * disk */ private boolean render() { LayoutEditor layoutEditor = mPalette.getEditor().getLayoutEditor(); @@ -209,10 +225,21 @@ public class PreviewIconFactory { // TODO - use resource resolution instead? if (mBackground == null) { - int rgb = image.getRGB(image.getWidth()-1, image.getHeight()-1); - RGB color = new RGB((rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8, - rgb & 0xFF); - storeBackground(imageDir, color); + Pair<RGB, RGB> themeColors = getColorsFromTheme(); + + RGB bg = themeColors.getFirst(); + RGB fg = themeColors.getSecond(); + + if (bg == null) { + int p = image.getRGB(image.getWidth() - 1, image.getHeight() - 1); + bg = new RGB((p & 0xFF0000) >> 16, (p & 0xFF00) >> 8, p & 0xFF); + // This isn't reliable - for example, for some themes the + // background is a 9 patch image - so in this case don't + // set the foreground color + fg = null; + } + + storeBackground(imageDir, bg, fg); } List<ViewInfo> viewInfoList = session.getRootViews(); @@ -220,6 +247,8 @@ public class PreviewIconFactory { // We don't render previews under a <merge> so there should // only be one root. ViewInfo firstRoot = viewInfoList.get(0); + int parentX = firstRoot.getLeft(); + int parentY = firstRoot.getTop(); List<ViewInfo> infos = firstRoot.getChildren(); for (ViewInfo info : infos) { Object cookie = info.getCookie(); @@ -233,10 +262,10 @@ public class PreviewIconFactory { // On Windows, perhaps we need to rename instead? file.delete(); } - int x1 = info.getLeft(); - int y1 = info.getTop(); - int x2 = info.getRight(); - int y2 = info.getBottom(); + int x1 = parentX + info.getLeft(); + int y1 = parentY + info.getTop(); + int x2 = parentX + info.getRight(); + int y2 = parentY + info.getBottom(); if (x1 != x2 && y1 != y2) { savePreview(file, image, x1, y1, x2, y2); } @@ -252,6 +281,125 @@ public class PreviewIconFactory { return true; } + /** + * Look up the background and foreground colors from the theme. May not find either + * the background or foreground or both, but will always return a pair of possibly + * null colors. + * + * @return a pair of possibly null color descriptions + */ + private Pair<RGB, RGB> getColorsFromTheme() { + RGB background = null; + RGB foreground = null; + + ResourceResolver resources = mPalette.getEditor().createResolver(); + StyleResourceValue theme = resources.getCurrentTheme(); + if (theme != null) { + background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$ + if (background == null) { + background = resolveThemeColor(resources, "colorBackground"); //$NON-NLS-1$ + } + foreground = resolveThemeColor(resources, "textColorPrimary"); //$NON-NLS-1$ + } + + return Pair.of(background, foreground); + } + + private static RGB resolveThemeColor(ResourceResolver resources, String resourceName) { + ResourceValue textColor = resources.findItemInTheme(resourceName); + textColor = resources.resolveResValue(textColor); + if (textColor == null) { + return null; + } + String value = textColor.getValue(); + + while (value != null) { + if (value.startsWith("#")) { //$NON-NLS-1$ + try { + int rgba = ImageUtils.getColor(value); + // Drop alpha channel + return new RGB((rgba & 0xFF0000) >> 16, (rgba & 0xFF00) >> 8, rgba & 0xFF); + } catch (NumberFormatException nfe) { + ; + } + return null; + } + if (value.startsWith("@")) { //$NON-NLS-1$ + boolean isFramework = textColor.isFramework(); + textColor = resources.findResValue(value, isFramework); + if (textColor != null) { + value = textColor.getValue(); + } else { + break; + } + } else { + File file = new File(value); + if (file.exists() && file.getName().endsWith(DOT_XML)) { + // Parse + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new FileInputStream(file)); + InputSource is = new InputSource(bis); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(is); + NodeList items = document.getElementsByTagName(TAG_ITEM); + + value = findColorValue(items); + continue; + } catch (Exception e) { + AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName()); + } finally { + if (bis != null) { + try { + bis.close(); + } catch (IOException e) { + // Nothing useful can be done here + } + } + } + } + + return null; + } + } + + return null; + } + + /** + * Searches a color XML file for the color definition element that does not + * have an associated state and returns its color + */ + private static String findColorValue(NodeList items) { + for (int i = 0, n = items.getLength(); i < n; i++) { + // Find non-state color definition + Node item = items.item(i); + boolean hasState = false; + if (item.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) item; + if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) { + NamedNodeMap attributes = element.getAttributes(); + for (int j = 0, m = attributes.getLength(); j < m; j++) { + Attr attribute = (Attr) attributes.item(j); + if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$ + hasState = true; + break; + } + } + + if (!hasState) { + return element.getAttributeNS(ANDROID_URI, ATTR_COLOR); + } + } + } + } + + return null; + } + private String getFileName(ElementDescriptor descriptor) { return descriptor.getUiName() + DOT_PNG; } @@ -304,8 +452,8 @@ public class PreviewIconFactory { if (themeName.startsWith(themeNamePrefix)) { themeName = themeName.substring(themeNamePrefix.length()); } - String dirName = String.format("palette-%s-%s-%s", - cleanup(targetName), cleanup(themeName), cleanup(mPalette.getCurrentDevice())); + String dirName = String.format("palette-preview-%s-%s-%s", cleanup(targetName), + cleanup(themeName), cleanup(mPalette.getCurrentDevice())); IPath dirPath = pluginState.append(dirName); mImageDir = new File(dirPath.toOSString()); @@ -328,21 +476,50 @@ public class PreviewIconFactory { } } - private void storeBackground(File imageDir, RGB color) { - mBackground = color; + private void storeBackground(File imageDir, RGB bg, RGB fg) { + mBackground = bg; + mForeground = fg; File file = new File(imageDir, PREVIEW_INFO_FILE); - String colors = String.format("background=%02x,%02x,%02x", - color.red, color.green, color.blue); + String colors = String.format( + "background=#%02x%02x%02x\nforeground=#%02x%02x%02x\\n", //$NON-NLS-1$ + bg.red, bg.green, bg.blue, + fg.red, fg.green, fg.blue); AdtPlugin.writeFile(file, colors); } public RGB getBackgroundColor() { if (mBackground == null) { - mBackground = null; + initColors(); + } + + return mBackground; + } + + public RGB getForegroundColor() { + if (mForeground == null) { + initColors(); + } + + return mForeground; + } + + public void initColors() { + try { + // Already initialized? Foreground can be null which would call + // initColors again and again, but background is never null after + // initialization so we use it as the have-initialized flag. + if (mBackground != null) { + return; + } File imageDir = getImageDir(false); if (!imageDir.exists()) { render(); + + // Initialized as part of the render + if (mBackground != null) { + return; + } } File file = new File(imageDir, PREVIEW_INFO_FILE); @@ -366,22 +543,22 @@ public class PreviewIconFactory { String colorString = (String) properties.get("background"); //$NON-NLS-1$ if (colorString != null) { - String[] colors = colorString.split(","); //$NON-NLS-1$ - if (colors.length == 3) { - colorString = colorString.trim(); - int red = Integer.parseInt(colors[0], 16); - int green = Integer.parseInt(colors[1], 16); - int blue = Integer.parseInt(colors[2], 16); - mBackground = new RGB(red, green, blue); - } + int rgb = ImageUtils.getColor(colorString.trim()); + mBackground = new RGB((rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8, rgb & 0xFF); + } + colorString = (String) properties.get("foreground"); //$NON-NLS-1$ + if (colorString != null) { + int rgb = ImageUtils.getColor(colorString.trim()); + mForeground = new RGB((rgb & 0xFF0000) >> 16, (rgb & 0xFF00) >> 8, rgb & 0xFF); } } if (mBackground == null) { - mBackground = new RGB(0,0,0); + mBackground = new RGB(0, 0, 0); } + // mForeground is allowed to be null. + } catch (Throwable t) { + AdtPlugin.log(t, "Cannot initialize preview color settings"); } - - return mBackground; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java index c611bab..73129f4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtilsTest.java @@ -298,5 +298,9 @@ public class ImageUtilsTest extends TestCase { assertEquals(0xFFFF0000, sub.getRGB(9, 9)); } - + public void testGetColor() throws Exception { + assertEquals(0xFF000000, ImageUtils.getColor("#000")); + assertEquals(0xFF000000, ImageUtils.getColor("#000000")); + assertEquals(0xABCDEF91, ImageUtils.getColor("#ABCDEF91")); + } } |