You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@netbeans.apache.org by GitBox <gi...@apache.org> on 2018/10/30 21:16:43 UTC

[GitHub] eirikbakke closed pull request #859: HiDPI icons for window system icons on Windows and Mac

eirikbakke closed pull request #859: HiDPI icons for window system icons on Windows and Mac
URL: https://github.com/apache/incubator-netbeans/pull/859
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
index 69f62340ab..a30081207d 100644
--- a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
+++ b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
@@ -117,8 +117,8 @@
 
 
             //UI Delegates for the tab control
-            EDITOR_TAB_DISPLAYER_UI, "org.netbeans.swing.tabcontrol.plaf.AquaEditorTabDisplayerUI",
-            VIEW_TAB_DISPLAYER_UI, "org.netbeans.swing.tabcontrol.plaf.AquaViewTabDisplayerUI",
+            EDITOR_TAB_DISPLAYER_UI, "org.netbeans.swing.tabcontrol.plaf.AquaVectorEditorTabDisplayerUI",
+            VIEW_TAB_DISPLAYER_UI, "org.netbeans.swing.tabcontrol.plaf.AquaVectorViewTabDisplayerUI",
             SLIDING_TAB_BUTTON_UI, "org.netbeans.swing.tabcontrol.plaf.SlidingTabDisplayerButtonUI$Aqua",
             "NbTabControl.focusedTabBackground", new Color(135,189,255),
             "NbTabControl.selectedTabBrighterBackground", new Color(252,252,252),
diff --git a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
index a5c45c95e8..3200d20388 100644
--- a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
+++ b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
@@ -77,9 +77,9 @@
     @Override
     public Object[] createApplicationSpecificKeysAndValues () {
         UIBootstrapValue editorTabsUI = new Windows8EditorColorings (
-                "org.netbeans.swing.tabcontrol.plaf.Windows8EditorTabDisplayerUI");
+                "org.netbeans.swing.tabcontrol.plaf.Windows8VectorEditorTabDisplayerUI");
 
-        Object viewTabsUI = editorTabsUI.createShared("org.netbeans.swing.tabcontrol.plaf.Windows8ViewTabDisplayerUI");
+        Object viewTabsUI = editorTabsUI.createShared("org.netbeans.swing.tabcontrol.plaf.Windows8VectorViewTabDisplayerUI");
 
         //TODO change icon (copy & paste)
         Image explorerIcon = UIUtils.loadImage("org/netbeans/swing/plaf/resources/vista_folder.png");
diff --git a/platform/o.n.swing.tabcontrol/nbproject/project.xml b/platform/o.n.swing.tabcontrol/nbproject/project.xml
index 7412416029..5c88d9844f 100644
--- a/platform/o.n.swing.tabcontrol/nbproject/project.xml
+++ b/platform/o.n.swing.tabcontrol/nbproject/project.xml
@@ -38,7 +38,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.3</specification-version>
+                        <specification-version>9.12</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
index 80f8e89806..cd74a43179 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
@@ -28,7 +28,7 @@
  *
  * @author S. Aubrecht
  */
-final class AquaEditorTabCellRenderer extends AbstractTabCellRenderer {
+class AquaEditorTabCellRenderer extends AbstractTabCellRenderer {
     //Default insets values for Mac look and feel
     private static final int TOP_INSET = 0;
     private static final int LEFT_INSET = 3;
@@ -106,6 +106,21 @@ public Color getSelectedForeground() {
         return getTxtColor();
     }
 
+    /**
+     * Returns icon which is correct for currect state of tab at given index
+     */
+    protected Icon findIcon() {
+        final String file;
+        if( inCloseButton() && isPressed() ) {
+            file = "org/openide/awt/resources/mac_close_pressed.png"; // NOI18N
+        } else if( inCloseButton() ) {
+            file = "org/openide/awt/resources/mac_close_rollover.png"; // NOI18N
+        } else {
+            file = "org/openide/awt/resources/mac_close_enabled.png"; // NOI18N
+        }
+        return TabControlButtonFactory.getIcon(file);
+    }
+
     private static void paintTabGradient( Graphics g, AquaEditorTabCellRenderer ren, Polygon poly ) {
         Rectangle rect = poly.getBounds();
         boolean selected = ren.isSelected();
@@ -166,8 +181,7 @@ public void getCloseButtonRectangle(JComponent jc,
                 rect.height = 0;
                 return;
             }
-            String iconPath = findIconPath(ren);
-            Icon icon = TabControlButtonFactory.getIcon(iconPath);
+            Icon icon = ren.findIcon();
             int iconWidth = icon.getIconWidth();
             int iconHeight = icon.getIconHeight();
             rect.x = bounds.x + bounds.width - iconWidth - 5;
@@ -176,21 +190,6 @@ public void getCloseButtonRectangle(JComponent jc,
             rect.height = iconHeight;
         }
 
-
-        /**
-         * Returns path of icon which is correct for currect state of tab at given
-         * index
-         */
-        private String findIconPath( AquaEditorTabCellRenderer renderer ) {
-            if( renderer.inCloseButton() && renderer.isPressed() ) {
-                return "org/openide/awt/resources/mac_close_pressed.png"; // NOI18N
-            }
-            if( renderer.inCloseButton() ) {
-                return "org/openide/awt/resources/mac_close_rollover.png"; // NOI18N
-            }
-            return "org/openide/awt/resources/mac_close_enabled.png"; // NOI18N
-        }
-
         public Polygon getInteriorPolygon(Component c) {
             AquaEditorTabCellRenderer ren = (AquaEditorTabCellRenderer) c;
 
@@ -277,8 +276,7 @@ public void paintInterior(Graphics g, Component c) {
             }
 
             //paint close button
-            String iconPath = findIconPath( ren );
-            Icon icon = TabControlButtonFactory.getIcon( iconPath );
+            Icon icon = ren.findIcon();
             icon.paintIcon(ren, g, r.x, r.y);
         }
 
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java
new file mode 100644
index 0000000000..b37007cbf3
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+
+/**
+ * A variation on the Aqua editor tab cell renderer that uses scalable icons for Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+final class AquaVectorEditorTabCellRenderer extends AquaEditorTabCellRenderer {
+    @Override
+    protected Icon findIcon() {
+        /* The "mac_close_(enabled|pressed|rollover).png" files were confirmed to be identical to
+        the mac_bigclose_(enabled|pressed|rollover).png ones. So we can use the same icons as in the
+        tab control here. */
+        if( inCloseButton() && isPressed() ) {
+            return AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_PRESSED);
+        } else if( inCloseButton() ) {
+            return AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_ROLLOVER);
+        } else {
+            return AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_DEFAULT);
+        }
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java
new file mode 100644
index 0000000000..446afd50f1
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Aqua editor tab displayer UI that uses scalable icons for Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+public final class AquaVectorEditorTabDisplayerUI extends AquaEditorTabDisplayerUI {
+    public AquaVectorEditorTabDisplayerUI(TabDisplayer displayer) {
+        super(displayer);
+    }
+
+    public static ComponentUI createUI(JComponent c) {
+        return new AquaVectorEditorTabDisplayerUI((TabDisplayer) c);
+    }
+
+    @Override
+    protected TabCellRenderer createDefaultRenderer() {
+        return new AquaVectorEditorTabCellRenderer();
+    }
+
+    @Override
+    public Icon getButtonIcon(int buttonId, int buttonState) {
+        Icon ret = AquaVectorTabControlIcon.get(buttonId, buttonState);
+        return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java
new file mode 100644
index 0000000000..f1bbdfaae3
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import org.openide.util.VectorIcon;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.swing.Icon;
+
+/**
+ * Scalable vector icons for the Aqua tab control L&amp;F. These icons look good both at the
+ * regular 100% scale as well as at the 200% scale used on Retina screens. At 100% scale, they are
+ * sized and aligned exactly like the bitmap icons that were previously used for the Aqua LAF, and
+ * look mostly the same, with a few updates to match current design standards:
+ *
+ * <ul>
+ *   <li>The slight bevel effect that was present on some of the bitmap buttons, and the slight
+ *       gradient that was present on the circular bitmap buttons, has been dropped in the name of
+ *       "flat design". (Apple HID guidelines say
+ *       <a href="https://developer.apple.com/design/human-interface-guidelines/macos/buttons/bevel-buttons">"Avoid
+ *       using bevel buttons"</a>.)
+ *   <li>Rounded rectangle buttons, including the segmented "scroll left/right" buttons, have had
+ *       their rounding radius increased slightly, to match that of
+ *       <a href="https://developer.apple.com/design/human-interface-guidelines/macos/selectors/segmented-controls">segmented controls</a>
+ *       on MacOS High Sierra.
+ *   <li>The smaller buttons now only show their solid backgrounds on rollover and press. This
+ *       reduces visual clutter, and is consistent with buttons in XCode, Finder, Chrome, and
+ *       Photoshop on MacOS High Sierra. To remain visible, some of these icons have had their
+ *       contents enlarged slightly.
+ *   <li>Except for the "x" button that closes a tab, the background shape of the small buttons have
+ *       been changed from a circle to a rounded rectangle, for consistency with other MacOS
+ *       apps (e.g. the "Mailboxes" icon toolbar button in the Mail app, or the "Refresh" button on
+ *       Chrome). This also allows the symbols inside to be made slightly larger; see above. (Apple
+ *       HID guidelines:
+ *       <a href="https://developer.apple.com/design/human-interface-guidelines/macos/buttons/round-buttons">"Avoid
+ *       using round buttons."</a>.)
+ *   <li>The circular "x" that closes a tab is given a red color on rollover and press, like in
+ *       Chrome for MacOS (and Windows), and like on NetBeans' Windows 8 LAF.
+ *   <li>Some slight brightness variations that were present between the previous bitmap button
+ *       types have been dropped, as these seemed to be accidental.
+ * </ul>
+ *
+ * @author Eirik Bakke
+ */
+@SuppressWarnings("serial")
+final class AquaVectorTabControlIcon extends VectorIcon {
+    private static final Map<Entry<Integer,Integer>,Icon> INSTANCES = populateInstances();
+    private final int buttonId;
+    private final int buttonState;
+
+    private static void populateOne(
+            Map<Entry<Integer,Integer>,Icon> toMap, int buttonId, int buttonState)
+    {
+        final int width;
+        final int height;
+        switch (buttonId) {
+            case TabControlButton.ID_CLOSE_BUTTON:
+                width = 14;
+                height = 12;
+                break;
+            case TabControlButton.ID_RESTORE_GROUP_BUTTON:
+            case TabControlButton.ID_SLIDE_GROUP_BUTTON:
+                width = 16;
+                height = 16;
+                break;
+            case TabControlButton.ID_PIN_BUTTON:
+                /* The pin button is shown next to the close button of a minimized panel that is
+                shown temporarily when the user hovers over its icon. So it must be the same size as
+                the close button. */
+                width = 14;
+                height = 12;
+                break;
+            case TabControlButton.ID_SCROLL_LEFT_BUTTON:
+                width = 26;
+                height = 15;
+                break;
+            case TabControlButton.ID_SCROLL_RIGHT_BUTTON:
+                width = 25;
+                height = 15;
+                break;
+            case TabControlButton.ID_DROP_DOWN_BUTTON:
+            case TabControlButton.ID_MAXIMIZE_BUTTON:
+            case TabControlButton.ID_RESTORE_BUTTON:
+                width = 20;
+                height = 15;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        toMap.put(new SimpleEntry<Integer,Integer>(buttonId, buttonState),
+                new AquaVectorTabControlIcon(buttonId, buttonState, width, height));
+    }
+
+    private static Map<Entry<Integer,Integer>,Icon> populateInstances() {
+        // The string keys of these maps aren't currently used, but are useful for debugging.
+        Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+        // ViewTabDisplayerUI
+        buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+        // These don't seem to be in use anymore.
+        //buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+        //buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+        //buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+        buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+        buttonIDs.put("restore_group", TabControlButton.ID_RESTORE_GROUP_BUTTON);
+        buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+        // EditorTabDisplayerUI
+        buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+        buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+        buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+        buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+        buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+        Map<String, Integer> buttonStates = new LinkedHashMap<String, Integer>();
+        buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+        buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+        buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+        buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+        Map<Entry<Integer,Integer>,Icon> ret = new LinkedHashMap<Entry<Integer,Integer>,Icon>();
+        for (Entry<String,Integer> buttonID : buttonIDs.entrySet()) {
+          for (Entry<String,Integer> buttonState : buttonStates.entrySet()) {
+              populateOne(ret, buttonID.getValue(), buttonState.getValue());
+          }
+        }
+        // Effectively immutable upon assignment to the final static variable.
+        return Collections.unmodifiableMap(ret);
+    }
+
+    private AquaVectorTabControlIcon(int buttonId, int buttonState, int width, int height) {
+        super(width, height);
+        this.buttonId = buttonId;
+        this.buttonState = buttonState;
+    }
+
+    /**
+     * @return null if the requested icon is not available in vector format
+     */
+    public static Icon get(int buttonId, int buttonState) {
+        return INSTANCES.get(new SimpleEntry<Integer,Integer>(buttonId, buttonState));
+    }
+
+    @Override
+    protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+        if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON ||
+            buttonId == TabControlButton.ID_RESTORE_BUTTON ||
+            buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+            buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+            buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+        {
+            paintLargerRectangleIcon(c, g, width, height, scaling);
+        } else if (buttonId == TabControlButton.ID_CLOSE_BUTTON) {
+            paintSmallCircleCloseIcon(c, g, width, height, scaling);
+        } else {
+            paintSmallRectangleIcon(c, g, width, height, scaling);
+        }
+    }
+
+    private void paintSmallCircleCloseIcon(
+            Component c, Graphics2D g, int width, int height, double scaling)
+    {
+        // Background circle diameter.
+        double d = Math.min(width, height);
+        Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no background.
+        /* Use transparency to achieve the right dark gray level, to make sure symbols are equally
+        visible on all backgrounds. */
+        Color fgColor = new Color(0, 0, 0, 168);
+        if (buttonState == TabControlButton.STATE_ROLLOVER) {
+            fgColor = Color.WHITE;
+            /* Red, with some transparency to blend onto the background. Chrome would have
+            (244, 65, 54, 255), here, but the value below works better with our expected
+            backgrounds. */
+            bgColor = new Color(255, 35, 25, 215);
+        } else if (buttonState == TabControlButton.STATE_PRESSED) {
+            fgColor = Color.WHITE;
+            // Slightly darker red. Chrome would have (196, 53, 43, 255) here; see above.
+            bgColor = new Color(185, 43, 33, 215);
+        } else if (buttonState == TabControlButton.STATE_DISABLED) {
+            // Light grey (via transparent black to work well on any background).
+            fgColor = new Color(0, 0, 0, 60);
+        }
+        if (bgColor.getAlpha() > 0) {
+            double circPosX = (width - d) / 2.0;
+            double circPosY = (height - d) / 2.0;
+            Shape bgCircle = new Ellipse2D.Double(circPosX, circPosY, d, d);
+            g.setColor(bgColor);
+            g.fill(bgCircle);
+        }
+        g.setColor(fgColor);
+        double strokeWidth = 1.4 * scaling;
+        // Middle x and y.
+        double mx = width / 2.0;
+        double my = height / 2.0;
+        // Radius of the cross ("X") symbol.
+        double cr = 0.45 * (d / 2.0);
+        /* Draw the "X". Fill the Shape of the entire cross rather than painting each line
+        separately as a Stroke, to avoid the intersecting area getting a higher opacity. */
+        Stroke stroke = new BasicStroke(
+                (float) strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+        Area area = new Area();
+        area.add(new Area(stroke.createStrokedShape(
+                new Line2D.Double(mx - cr, my - cr, mx + cr, my + cr))));
+        area.add(new Area(stroke.createStrokedShape(
+                new Line2D.Double(mx + cr, my - cr, mx - cr, my + cr))));
+        g.fill(area);
+    }
+
+    private void paintSmallRectangleIcon(
+            Component c, Graphics2D g, int width, int height, double scaling)
+    {
+        Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no background.
+        /* Use transparency to achieve the right dark gray level, to make sure symbols are equally
+        visible on all backgrounds. */
+        Color fgColor = new Color(0, 0, 0, 168);
+        if (buttonState == TabControlButton.STATE_DISABLED) {
+            // Light grey (via transparent black to work well on any background).
+            fgColor = new Color(0, 0, 0, 60);
+        } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+            /* Light grey (via transparent black), like in XCode tab close buttons, the Chrome
+            "refresh" button, or the Mail app's "Mailboxes" button (used the slightly darker level
+            from the latter). */
+            bgColor = new Color(0, 0, 0, 51);
+            fgColor = Color.WHITE;
+        } else if (buttonState == TabControlButton.STATE_PRESSED) {
+            /* Slightly darker light grey (via transparent black). Same as in the aforementioned
+            "Mailboxes" icon. */
+            bgColor = new Color(0, 0, 0, 94);
+            fgColor = Color.WHITE;
+        }
+        if (bgColor.getAlpha() > 0) {
+            /* Use the same rounding radius as in paintLargerRectangleIcon. Same as in the
+            aforementioned "Mailboxes" icon. */
+            double arc = scaling * 6.0;
+            Shape bgRect = new RoundRectangle2D.Double(0, 0, width, height, arc, arc);
+            g.setColor(bgColor);
+            g.fill(bgRect);
+        }
+        g.setColor(fgColor);
+        if (buttonId == TabControlButton.ID_RESTORE_GROUP_BUTTON) {
+            // Draw one little window on top of another.
+            int marginX = round(3 * scaling);
+            int marginY = round(3 * scaling);
+            int winWidth = round(7.0 * scaling);
+            int winHeight = round(6.0 * scaling);
+            // Upper right-hand corner.
+            int win1X = width - marginX - winWidth;
+            int win1Y = marginY;
+            /* Lower left-hand corner. Make sure the window symbols are not too close on any scaling
+            level. */
+            int win2X = Math.min((int) Math.floor(win1X - 2 * scaling), marginX);
+            int win2Y = Math.max((int) Math.ceil(win1Y + 2 * scaling), round(height - 9.5 * scaling));
+            Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth, winHeight);
+            Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth, winHeight);
+            // Make window 2 appear "on top of" window 1.
+            win1.subtract(new Area(win2.getBounds2D()));
+            g.fill(win1);
+            g.fill(win2);
+        } else if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+            int marginX = (int) (3 * scaling);
+            int marginTop = (int) (4 * scaling);
+            int marginBot = (int) (4 * scaling);
+            Area win = getWindowSymbol(scaling, marginX, marginTop,
+                    width - 2 * marginX,
+                    height - marginTop - marginBot);
+            g.fill(win);
+        } else if (buttonId == TabControlButton.ID_PIN_BUTTON) {
+            int marginX = (int) (3 * scaling);
+            int marginTop = (int) (2 * scaling);
+            int marginBot = (int) (2 * scaling);
+            Area win = getWindowSymbol(scaling, marginX, marginTop,
+                    width - 2 * marginX,
+                    height - marginTop - marginBot);
+            g.fill(win);
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    private void paintLargerRectangleIcon(
+            Component c, Graphics2D g, int width, int height, double scaling)
+    {
+        final Color bgTopColor;
+        final Color bgBotColor;
+        final Color symbolColor;
+        final Color borderColor;
+        // These colors taken from the previous bitmap icons.
+        if (buttonState == TabControlButton.STATE_DEFAULT) {
+            bgTopColor = new Color(191, 191, 191);
+            bgBotColor = new Color(135, 135, 135);
+            borderColor = new Color(81, 81, 81);
+            symbolColor = new Color(48, 48, 48);
+        } else if (buttonState == TabControlButton.STATE_PRESSED) {
+            bgTopColor = new Color(182, 182, 182);
+            bgBotColor = new Color(129, 129, 129);
+            borderColor = new Color(81, 81, 81);
+            symbolColor = new Color(45, 45, 45);
+        } else if (buttonState == TabControlButton.STATE_DISABLED) {
+            bgTopColor = new Color(166, 166, 166);
+            bgBotColor = new Color(137, 137, 137);
+            borderColor = new Color(111, 111, 111);
+            symbolColor = new Color(97, 97, 97);
+        } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+            bgTopColor = new Color(198, 198, 198);
+            bgBotColor = new Color(149, 149, 149);
+            borderColor = new Color(81, 81, 81);
+            symbolColor = new Color(77, 77, 77);
+        } else {
+            throw new IllegalArgumentException();
+        }
+        /* Pick a stroke width that will make the outer border 1 physical pixel wide on both 100%
+        and 200% (Retina) scaling, for consistency with native segmented controls. */
+        int strokeWidth = round(0.6 * scaling);
+        g.setPaint(new GradientPaint(new Point2D.Double(0, strokeWidth),
+                bgTopColor,
+                new Point2D.Double(0, height - strokeWidth),
+                bgBotColor));
+        /* Make the scroll left and right buttons extend beyond their right and left edges,
+        respectively. Then clip them at the icon's dimensions to get the correct segmented control
+        effect. */
+        int rectExtraDir;
+        if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+            rectExtraDir = 1;
+        } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+            rectExtraDir = -1;
+        } else {
+            rectExtraDir = 0;
+        }
+        /* Use a rounded rectangle radius consistent with that of segmented buttons and comboboxes
+        on MacOS High Sierra. (To match the old bitmap Aqua LAF exactly, we could have used 4.0
+        here instead.) */
+        double arc = scaling * 6.0;
+        double rectExtraX = rectExtraDir * (strokeWidth + arc);
+        Shape rect = new RoundRectangle2D.Double(
+                strokeWidth / 2.0 + (rectExtraDir < 0 ? rectExtraX : 0),
+                strokeWidth / 2.0,
+                width - strokeWidth + Math.abs(rectExtraX),
+                height - strokeWidth, arc, arc);
+        g.clipRect(0, 0, width, height);
+        // Draw the gradient background of the rounded rectangle.
+        g.fill(rect);
+        // Now draw the border around the rounded rectangle.
+        g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+        g.setColor(borderColor);
+        g.draw(rect);
+        // The width to use for centering.
+        int useWidth;
+        if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+            // The scroll left button includes the separator line against the scroll right button.
+            g.fillRect(width - strokeWidth, 0, strokeWidth, height);
+            useWidth = width - strokeWidth;
+        } else {
+            useWidth = width;
+        }
+        g.setColor(symbolColor);
+        if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON) {
+            int marginX = round(4 * scaling);
+            int marginTop = round(3 * scaling);
+            int marginBot = round(3 * scaling);
+            /* Draw one larger window symbol. The getWindowSymbol method ensures we are using the
+            same window border thickness as for ID_RESTORE_BUTTON. */
+            g.fill(getWindowSymbol(scaling, marginX, marginTop,
+                    width - 2 * marginX, height - marginTop - marginBot));
+        } else if (buttonId == TabControlButton.ID_RESTORE_BUTTON) {
+            // Draw one little window on top of another.
+            int marginX = round(4 * scaling);
+            int marginTop = round(2 * scaling);
+            int marginBot = round(2.5 * scaling);
+            int winWidth = round(9 * scaling);
+            int winHeight = round(7.0 * scaling);
+            // Upper right-hand corner.
+            int win1X = width - marginX - winWidth;
+            int win1Y = marginTop;
+            /* Lower left-hand corner. Make sure the window symbols are not too close on any scaling
+            level. */
+            int win2X = Math.min((int) Math.floor(win1X - 2 * scaling), marginX);
+            int win2Y = Math.max(win1Y + round(2.7 * scaling), height - winHeight - marginBot);
+            Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth, winHeight);
+            Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth, winHeight);
+            // Make window 2 appear "on top of" window 1.
+            win1.subtract(new Area(win2.getBounds2D()));
+            g.fill(win1);
+            g.fill(win2);
+        } else if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+            buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+            buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+        {
+            if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+                // Rotate 90 degrees clockwise, with a small position adjustment.
+                g.translate(round(1 * scaling), 0);
+                g.rotate(Math.PI / 2.0, useWidth / 2.0, height / 2.0);
+            } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+                // Rotate 90 degrees counterclockwise, with a small position adjustment.
+                g.translate(-round(1 * scaling), 0);
+                g.rotate(-Math.PI / 2.0, useWidth / 2.0, height / 2.0);
+            }
+            double arrowWidth, arrowHeight;
+            if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON) {
+                /* Make the arrow a tiny bit wider than in the old bitmap icons here. Using
+                arrowWidth = 5.0 would have given the exact same dimensions as the old bitmap icons
+                at 100% scaling. */
+                arrowWidth = 6.0 * scaling;
+                arrowHeight = 4.0 * scaling;
+            } else {
+                // These dimensions match the old bitmap icons at 100% scaling.
+                arrowWidth = 6.7 * scaling;
+                arrowHeight = 3.8 * scaling;
+            }
+
+            /* Draw a simple arrowhead triangle pointing downwards (before any rotations). Keep the
+            top line aligned to device pixels. No need to round the other positions. */
+            final int y = round((height - arrowHeight) / 2.0);
+            final double marginX = (useWidth - arrowWidth) / 2.0;
+            final double arrowMidX = marginX + arrowWidth / 2.0;
+            Path2D.Double arrowPath = new Path2D.Double();
+            arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+            arrowPath.lineTo(arrowMidX, y + arrowHeight);
+            arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+            arrowPath.closePath();
+            g.fill(arrowPath);
+        }
+    }
+
+    /**
+     * Make a small window symbol. This is used in several of the icons here. All coordinates are
+     * in device pixels.
+     */
+    private Area getWindowSymbol(double scaling, int x, int y, int width, int height) {
+        /* Pick a thickness that will make the window symbol border 2 physical pixels wide at 200%
+        scaling, to look consistent with the rest of the UI, including borders and icons that do not
+        have any special Retina support. */
+        int borderThickness = round(0.8 * scaling);
+        int titleBarHeight =
+                (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON ||
+                buttonId == TabControlButton.ID_PIN_BUTTON)
+                ? borderThickness
+                : Math.max(round(1.6 * scaling), borderThickness + height / 7);
+        int windowX = round(x);
+        int windowY = round(y);
+        Area ret = new Area(new Rectangle2D.Double(
+                windowX, windowY, width, height));
+        ret.subtract(new Area(new Rectangle2D.Double(
+                windowX + borderThickness, windowY + titleBarHeight,
+                width - borderThickness * 2,
+                height - borderThickness - titleBarHeight)));
+        if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+            ret.add(new Area(new Rectangle2D.Double(
+                windowX + borderThickness * 2,
+                windowY + height - borderThickness * 4,
+                round((width - borderThickness * 4) * 0.67),
+                borderThickness * 2)));
+        } else if (buttonId == TabControlButton.ID_PIN_BUTTON) {
+            int marginX = round(width * 0.3);
+            int marginY = round(height * 0.3);
+            ret.add(new Area(new Rectangle2D.Double(
+                windowX + marginX, windowY + marginY,
+                width - marginX * 2,
+                height - marginY * 2)));
+        }
+        return ret;
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java
new file mode 100644
index 0000000000..711ff299a7
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Aqua view tab displayer UI that uses scalable icons for Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+public final class AquaVectorViewTabDisplayerUI extends AquaViewTabDisplayerUI {
+    private AquaVectorViewTabDisplayerUI(TabDisplayer displayer) {
+        super(displayer);
+    }
+
+    public static ComponentUI createUI(JComponent c) {
+        return new AquaVectorViewTabDisplayerUI((TabDisplayer) c);
+    }
+
+    @Override
+    public Icon getButtonIcon(int buttonId, int buttonState) {
+        Icon ret = AquaVectorTabControlIcon.get(buttonId, buttonState);
+        return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
index 5d48bc1e90..c5afc65fe9 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
@@ -46,7 +46,7 @@
  *
  * @author Tim Boudreau
  */
-public final class AquaViewTabDisplayerUI extends AbstractViewTabDisplayerUI {
+public class AquaViewTabDisplayerUI extends AbstractViewTabDisplayerUI {
 
     private static final int TXT_X_PAD = 5;
     private static final int ICON_X_PAD = 2;
@@ -64,7 +64,7 @@
     /**
      * Should be constructed only from createUI method.
      */
-    private AquaViewTabDisplayerUI(TabDisplayer displayer) {
+    protected AquaViewTabDisplayerUI(TabDisplayer displayer) {
         super(displayer);
         prefSize = new Dimension(100, 19); //XXX huh?
     }
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
index 25ec17730e..e789d34765 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
@@ -29,7 +29,6 @@
 import java.util.logging.Logger;
 import javax.swing.Action;
 import javax.swing.Icon;
-import javax.swing.JButton;
 import javax.swing.SwingUtilities;
 import javax.swing.Timer;
 import javax.swing.ToolTipManager;
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
index 2396eaeb1b..b6d729b840 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
@@ -256,17 +256,19 @@ protected void paintIconAndText( Graphics g ) {
     }
 
     /**
-     * Returns path of icon which is correct for currect state of tab at given
+     * Returns the icon which is correct for currect state of tab at given
      * index
      */
-    String findIconPath() {
+    Icon findIcon() {
+        final String file;
         if( inCloseButton() && isPressed() ) {
-            return "org/openide/awt/resources/vista_close_pressed.png"; // NOI18N
-        }
-        if( inCloseButton() ) {
-            return "org/openide/awt/resources/vista_close_rollover.png"; // NOI18N
+            file = "org/openide/awt/resources/vista_close_pressed.png"; // NOI18N
+        } else if ( inCloseButton() ) {
+            file = "org/openide/awt/resources/vista_close_rollover.png"; // NOI18N
+        } else {
+            file = "org/openide/awt/resources/vista_close_enabled.png"; // NOI18N
         }
-        return "org/openide/awt/resources/vista_close_enabled.png"; // NOI18N
+        return TabControlButtonFactory.getIcon(file);
     }
 
     private static class WinVistaPainter implements TabPainter {
@@ -290,8 +292,7 @@ public void getCloseButtonRectangle(JComponent jc,
                 rect.height = 0;
                 return;
             }
-            String iconPath = ren.findIconPath();
-            Icon icon = TabControlButtonFactory.getIcon(iconPath);
+            Icon icon = ren.findIcon();
             int iconWidth = icon.getIconWidth();
             int iconHeight = icon.getIconHeight();
             rect.x = bounds.x + bounds.width - iconWidth - 2;
@@ -389,8 +390,7 @@ public void paintInterior(Graphics g, Component c) {
             }
             
             //paint close button
-            String iconPath = ren.findIconPath();
-            Icon icon = TabControlButtonFactory.getIcon( iconPath );
+            Icon icon = ren.findIcon();
             icon.paintIcon(ren, g, r.x, r.y);
         }
 
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
index 5c58a37108..6723ad722c 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
@@ -24,6 +24,7 @@
 package org.netbeans.swing.tabcontrol.plaf;
 
 import java.awt.*;
+import javax.swing.Icon;
 
 /**
  * Windows 8 implementation of tab renderer
@@ -31,7 +32,7 @@
  * @author S. Aubrecht
  * @since 1.41
  */
-final class Windows8EditorTabCellRenderer extends WinVistaEditorTabCellRenderer {
+class Windows8EditorTabCellRenderer extends WinVistaEditorTabCellRenderer {
 
     public Windows8EditorTabCellRenderer() {
     }
@@ -46,18 +47,16 @@ void paintTabGradient( Graphics g, Polygon poly ) {
         Windows8ViewTabDisplayerUI.paintTabBackground( (Graphics2D)g, rect.x, rect.y, rect.width, rect.height, selected, focused, attention, mouseOver);
     }
 
-    /**
-     * Returns path of icon which is correct for currect state of tab at given
-     * index
-     */
     @Override
-    String findIconPath() {
+    Icon findIcon() {
+        final String file;
         if( inCloseButton() && isPressed() ) {
-            return "org/openide/awt/resources/win8_bigclose_pressed.png"; // NOI18N
+            file = "org/openide/awt/resources/win8_bigclose_pressed.png"; // NOI18N
+        } else if( inCloseButton() ) {
+            file = "org/openide/awt/resources/win8_bigclose_rollover.png"; // NOI18N
+        } else {
+            file = "org/openide/awt/resources/win8_bigclose_enabled.png"; // NOI18N
         }
-        if( inCloseButton() ) {
-            return "org/openide/awt/resources/win8_bigclose_rollover.png"; // NOI18N
-        }
-        return "org/openide/awt/resources/win8_bigclose_enabled.png"; // NOI18N
+        return TabControlButtonFactory.getIcon(file);
     }
 }
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
index 74612d0d40..f8d2d8b0d2 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
@@ -32,7 +32,7 @@
  * @author S. Aubrecht
  * @since 1.41
  */
-public final class Windows8EditorTabDisplayerUI extends AbstractWinEditorTabDisplayerUI {
+public class Windows8EditorTabDisplayerUI extends AbstractWinEditorTabDisplayerUI {
 
     private static Map<Integer, String[]> buttonIconPaths;
     
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java
new file mode 100644
index 0000000000..984bfc7a21
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+
+/**
+ * A variation on the Windows 8 editor tab cell renderer that uses scalable icons for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look the same.
+ */
+final class Windows8VectorEditorTabCellRenderer extends Windows8EditorTabCellRenderer {
+    @Override
+    Icon findIcon() {
+        if( inCloseButton() && isPressed() ) {
+            return Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_PRESSED);
+        } else if( inCloseButton() ) {
+            return Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_ROLLOVER);
+        } else {
+            return Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON, TabControlButton.STATE_DEFAULT);
+        }
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java
new file mode 100644
index 0000000000..a4ae65f472
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Windows 8 editor tab displayer UI that uses scalable icons for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look the same.
+ */
+public final class Windows8VectorEditorTabDisplayerUI extends Windows8EditorTabDisplayerUI {
+    public Windows8VectorEditorTabDisplayerUI(TabDisplayer displayer) {
+        super(displayer);
+    }
+
+    public static ComponentUI createUI(JComponent c) {
+        return new Windows8VectorEditorTabDisplayerUI((TabDisplayer) c);
+    }
+
+    @Override
+    protected TabCellRenderer createDefaultRenderer() {
+        return new Windows8VectorEditorTabCellRenderer();
+    }
+
+    @Override
+    public Icon getButtonIcon(int buttonId, int buttonState) {
+        Icon ret = Windows8VectorTabControlIcon.get(buttonId, buttonState);
+        return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java
new file mode 100644
index 0000000000..9e14b5f794
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import org.openide.util.VectorIcon;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.swing.Icon;
+
+/**
+ * Scalable vector icons for the Windows tab control L&amp;F, for use with HiDPI screens. These
+ * icons look good at all of the standard scaling factors available on Windows (see superclass
+ * Javadoc). At 100% scale, they look nearly identical to bitmap icons that were used previously for
+ * the Windows 8 LAF.
+ *
+ * @author Eirik Bakke
+ */
+@SuppressWarnings("serial")
+final class Windows8VectorTabControlIcon extends VectorIcon {
+    private static final Map<Entry<Integer,Integer>,Icon> INSTANCES = populateInstances();
+    private final int buttonId;
+    private final int buttonState;
+
+    private static void populateOne(
+            Map<Entry<Integer,Integer>,Icon> toMap, int buttonId, int buttonState)
+    {
+        toMap.put(new SimpleEntry<Integer,Integer>(buttonId, buttonState),
+                new Windows8VectorTabControlIcon(buttonId, buttonState));
+    }
+
+    private static Map<Entry<Integer,Integer>,Icon> populateInstances() {
+        // The string keys of these maps aren't currently used, but are useful for debugging.
+        Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+        // ViewTabDisplayerUI
+        buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+        /* These don't seem to be in use anymore. Or at least they don't have modernized icons in
+        the Windows8 LAF. */
+        //buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+        //buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+        //buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+        buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+        buttonIDs.put("restore_group", TabControlButton.ID_RESTORE_GROUP_BUTTON);
+        buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+        // EditorTabDisplayerUI
+        buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+        buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+        buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+        buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+        buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+        Map<String, Integer> buttonStates = new LinkedHashMap<String, Integer>();
+        buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+        buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+        buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+        buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+        Map<Entry<Integer,Integer>,Icon> ret = new LinkedHashMap<Entry<Integer,Integer>,Icon>();
+        for (Entry<String,Integer> buttonID : buttonIDs.entrySet()) {
+          for (Entry<String,Integer> buttonState : buttonStates.entrySet()) {
+              populateOne(ret, buttonID.getValue(), buttonState.getValue());
+          }
+        }
+        // Effectively immutable upon assignment to the final static variable.
+        return Collections.unmodifiableMap(ret);
+    }
+
+    private Windows8VectorTabControlIcon(int buttonId, int buttonState) {
+        super(14, 14);
+        this.buttonId = buttonId;
+        this.buttonState = buttonState;
+    }
+
+    /**
+     * @return null if the requested icon is not available in vector format
+     */
+    public static Icon get(int buttonId, int buttonState) {
+        return INSTANCES.get(new SimpleEntry<Integer,Integer>(buttonId, buttonState));
+    }
+
+    @Override
+    protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+        Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no background.
+        Color fgColor = new Color(86, 86, 86, 255);
+        {
+            Color closeColor = (buttonId == TabControlButton.ID_CLOSE_BUTTON)
+                    // A nice red.
+                    ? new Color(199, 79, 80, 255) : null;
+            if (buttonState == TabControlButton.STATE_DISABLED) {
+                // Light grey (via transparent black to work well on any background).
+                fgColor = new Color(0, 0, 0, 45);
+            } else if (buttonState == TabControlButton.STATE_PRESSED) {
+                // A nice blue.
+                bgColor = closeColor != null ? closeColor : new Color(57, 100, 178, 255);
+                fgColor = Color.WHITE;
+            } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+                bgColor = closeColor != null ? closeColor
+                        // Grey (via transparent black to work well on any background).
+                        : new Color(0, 0, 0, 70);
+                fgColor = Color.WHITE;
+            }
+        }
+        if (bgColor.getAlpha() > 0) {
+            g.setColor(bgColor);
+            g.fillRect(0, 0, width, height);
+        }
+        g.setColor(fgColor);
+        if (buttonId == TabControlButton.ID_CLOSE_BUTTON) {
+            // Draw an "X" with a flat top and bottom.
+            if (getIconWidth() == width && getIconHeight() == height) {
+                // For the unscaled case, this icon looks better without anti-aliasing.
+                setAntiAliasing(g, false);
+            }
+            // Use a slightly heavier line when there's a non-light background.
+            double strokeWidth = (bgColor.getAlpha() > 0 ? 1.0 : 0.8) * scaling;
+            if (scaling > 1.0) {
+                // Use a heavier line when we have more pixels available.
+                strokeWidth *= 1.5f;
+            }
+            double marginX = 3.5 * scaling; // Don't round this one.
+            int topMarginY = round(3 * scaling);
+            int botMarginY = round(4 * scaling);
+            // Flatten the top and bottom.
+            g.clip(new Rectangle2D.Double(0, topMarginY, width, height - topMarginY - botMarginY));
+            // Draw the "X".
+            g.setStroke(new BasicStroke((float) strokeWidth));
+            g.draw(new Line2D.Double(marginX, topMarginY, width - marginX, height - botMarginY));
+            g.draw(new Line2D.Double(width - marginX, topMarginY, marginX, height - botMarginY));
+        } else if (buttonId == TabControlButton.ID_PIN_BUTTON ||
+                buttonId == TabControlButton.ID_RESTORE_GROUP_BUTTON ||
+                buttonId == TabControlButton.ID_RESTORE_BUTTON)
+        {
+            // Draw one little window on top of another.
+            int margin = round(2 * scaling);
+            int winWidth = round(6.5 * scaling);
+            int winHeight = round(5.5 * scaling);
+            // Upper right-hand corner.
+            int win1X = width - margin - winWidth;
+            int win1Y = margin;
+            // Lower left-hand corner.
+            int win2X = margin;
+            int win2Y = round(5.5 * scaling);
+            Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth, winHeight);
+            Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth, winHeight);
+            // Make window 2 appear "on top of" window 1.
+            win1.subtract(new Area(win2.getBounds2D()));
+            g.fill(win1);
+            g.fill(win2);
+        } else if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON) {
+            int marginX = round(2.2 * scaling);
+            int marginY = round(3 * scaling);
+            int windowHeight = round(7.5 * scaling);
+            /* Draw one larger window. The getWindowSymbol method ensures we are using the same
+            window border thickness as for ID_RESTORE_BUTTON. */
+            g.fill(getWindowSymbol(scaling, marginX, marginY, width - 2 * marginX, windowHeight));
+        } else if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+            // Draw a simple bar towards the bottom of the icon.
+            int marginX = round(2 * scaling);
+            int barX = marginX;
+            int barY = round(8 * scaling);
+            int barWidth = width - marginX * 2;
+            // Use the same thickness as the title bar in getWindowSymbol.
+            int barThickness = round(1.8 * scaling);
+            g.fill(new Rectangle2D.Double(barX, barY, barWidth, barThickness));
+        } else if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+                   buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+                   buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+        {
+            if (getIconWidth() == width && getIconHeight() == height) {
+                // For the regular 100% scaling level, this icon looks better without anti-aliasing.
+                setAntiAliasing(g, false);
+            }
+            if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+                // Rotate 90 degrees clockwise, with a small position adjustment.
+                g.translate(-round(1 * scaling), 0);
+                g.rotate(Math.PI / 2.0, width / 2.0, height / 2.0);
+            } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+                // Rotate 90 degrees counterclockwise, with a small position adjustment.
+                g.translate(round(1 * scaling), 0);
+                g.rotate(-Math.PI / 2.0, width / 2.0, height / 2.0);
+            }
+            /* Draw a simple arrowhead triangle pointing downwards (before any rotations). Keep the
+            top line pixel-aligned. No need to round the other positions. */
+            final int y = round(4.0 * scaling);
+            final double arrowWidth = (scaling == 1.0 ? 12.0 : 10.0) * scaling;
+            final double arrowHeight = 5.0 * scaling;
+            final double marginX = (width - arrowWidth) / 2.0;
+            final double arrowMidX = marginX + arrowWidth / 2.0;
+            Path2D.Double arrowPath = new Path2D.Double();
+            arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+            arrowPath.lineTo(arrowMidX, y + arrowHeight);
+            arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+            arrowPath.closePath();
+            g.fill(arrowPath);
+        }
+    }
+
+    /**
+     * Make a small window symbol (hollow rectangle with a thicker "title bar" on top). This is used
+     * in a couple of the icons here. All coordinates are in device pixels.
+     */
+    private static Area getWindowSymbol(
+            double scaling, int x, int y, int width, int height)
+    {
+        /* Pick a thickness that will make the window symbol border 2 physical pixels wide at 200%
+        scaling, to look consistent with the rest of the UI, including existing icons that do not
+        have any special HiDPI support. Lower scaling levels will yield a 1 physical pixel wide
+        border. */
+        int borderThickness = round(0.8 * scaling);
+        int titleBarHeight = Math.max(round(1.8 * scaling), borderThickness + 1);
+        Area ret = new Area(new Rectangle2D.Double(x, y, width, height));
+        ret.subtract(new Area(new Rectangle2D.Double(
+                x + borderThickness, y + titleBarHeight,
+                width - borderThickness * 2,
+                height - borderThickness - titleBarHeight)));
+        return ret;
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java
new file mode 100644
index 0000000000..b7a8a6f47f
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Windows 8 view tab displayer UI that uses scalable icons for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look the same.
+ */
+public final class Windows8VectorViewTabDisplayerUI extends Windows8ViewTabDisplayerUI {
+    private Windows8VectorViewTabDisplayerUI(TabDisplayer displayer) {
+        super(displayer);
+    }
+
+    public static ComponentUI createUI(JComponent c) {
+        return new Windows8VectorViewTabDisplayerUI((TabDisplayer) c);
+    }
+
+    @Override
+    public Icon getButtonIcon(int buttonId, int buttonState) {
+        Icon ret = Windows8VectorTabControlIcon.get(buttonId, buttonState);
+        return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+    }
+}
diff --git a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
index d5bbd57f75..208e4c294b 100644
--- a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
+++ b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
@@ -36,7 +36,7 @@
  * @author S. Aubrecht
  * @since 1.41
  */
-public final class Windows8ViewTabDisplayerUI extends AbstractWinViewTabDisplayerUI {
+public class Windows8ViewTabDisplayerUI extends AbstractWinViewTabDisplayerUI {
 
     /**
      * True when colors were already initialized, false otherwise
@@ -58,7 +58,7 @@
     /**
      * Should be constructed only from createUI method.
      */
-    private Windows8ViewTabDisplayerUI(TabDisplayer displayer) {
+    protected Windows8ViewTabDisplayerUI(TabDisplayer displayer) {
         super(displayer);
     }
 
diff --git a/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java b/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java
new file mode 100644
index 0000000000..0f0be3e7ab
--- /dev/null
+++ b/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java
@@ -0,0 +1,556 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.KeyStroke;
+import javax.swing.Scrollable;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.UIManager;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+import org.netbeans.swing.tabcontrol.TabDisplayerUI;
+import org.openide.util.ImageUtilities;
+
+/**
+ * Utility for previewing custom-painted vector icons, at various resolutions and comparing
+ * side-by-side with existing LAFs. When implementing new vector icons, invoke this utility using
+ * the "Debug" command in NetBeans, then invoke "Apply Code Changes" to have your latest changes
+ * immediately be reflected on the screen in this utility.
+ *
+ * <p>Each displayed column corresponds to one LAF or ComponentUI implementation, as specified in
+ * the getIcons method below. The last column displays either the first or the second LAF in the
+ * list, toggleable with the Shift key, to facilitate comparison and visual alignment. Pressing
+ * Space, or holding down Shift, will will enable a mode where the last column quickly toggles back
+ * and forth between the first two LAFs for comparison. A copy of the output with also be written to
+ * a PNG file in the temporary directory when the utility is first launched.
+ *
+ * <p>This utility is currently configured to show icons related to the NetBeans tabcontrol widget.
+ * It can be copied to other modules during development and reconfigured to show other icons, by
+ * modifying the {@code getIcons()} method.
+ *
+ * @author Eirik Bakke
+ */
+public class VectorIconTester extends javax.swing.JFrame {
+    private static final boolean TEST_AQUA = true;
+    private static final boolean TEST_WIN8 = true;
+    private final JScrollPane scrollPane;
+    private final IconPreviewPane iconPreviewPane;
+
+    public VectorIconTester() {
+        iconPreviewPane = new IconPreviewPane(getIcons());
+        scrollPane = new JScrollPane(iconPreviewPane);
+        initComponents();
+    }
+
+    public void dumpGraphicsToFile() {
+        final BufferedImage bi = new BufferedImage(
+                iconPreviewPane.getSize().width, iconPreviewPane.getSize().height,
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = bi.createGraphics();
+        try {
+            iconPreviewPane.paintComponent(g);
+        } finally {
+            g.dispose();
+        }
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    File tempFile = File.createTempFile("VectorIconTester", ".png");
+                    ImageIO.write(bi, "PNG", tempFile);
+                    System.out.println("Output was written to " + tempFile);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }).start();
+    }
+
+    private void initComponents() {
+        setTitle("Vector Icon Tester");
+        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+        setLayout(new BorderLayout());
+        add(scrollPane, BorderLayout.CENTER);
+        setSize(800, 600);
+        setExtendedState(JFrame.MAXIMIZED_BOTH);
+    }
+
+    private static Map<String, Icon> getIcons() {
+        Map<String, Icon> ret = new LinkedHashMap<String, Icon>();
+        if (TEST_AQUA) {
+            addTabDisplayerIcons(ret, "mac", (TabDisplayerUI) AquaViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "mac", (TabDisplayerUI) AquaEditorTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "macvec", (TabDisplayerUI) AquaVectorViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "macvec", (TabDisplayerUI) AquaVectorEditorTabDisplayerUI.createUI(new TabDisplayer()));
+        }
+        if (TEST_WIN8) {
+            addTabDisplayerIcons(ret, "win8", (TabDisplayerUI) Windows8ViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "win8", (TabDisplayerUI) Windows8EditorTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "win8vec", (TabDisplayerUI) Windows8VectorViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "win8vec", (TabDisplayerUI) Windows8VectorEditorTabDisplayerUI.createUI(new TabDisplayer()));
+        }
+
+        if (false) {
+            addTabDisplayerIcons(ret, "gtk", (TabDisplayerUI) GtkViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "gtk", (TabDisplayerUI) GtkEditorTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "nimbus", (TabDisplayerUI) NimbusViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "nimbus", (TabDisplayerUI) NimbusEditorTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "metal", (TabDisplayerUI) MetalViewTabDisplayerUI.createUI(new TabDisplayer()));
+            addTabDisplayerIcons(ret, "metal", (TabDisplayerUI) MetalEditorTabDisplayerUI.createUI(new TabDisplayer()));
+        }
+        if (false) {
+            // Icons that are not specialized by LAF.
+            ret.put("gen_arrow", ImageUtilities.loadImageIcon("org/openide/awt/resources/arrow.png", false));
+            ret.put("gen_busy_icon", ImageUtilities.loadImageIcon("org/netbeans/swing/tabcontrol/resources/busy_icon.png", false));
+            ret.put("gen_toolbar_arrow_horizontal", ImageUtilities.loadImageIcon("org/openide/awt/resources/toolbar_arrow_horizontal.png", false));
+            ret.put("gen_toolbar_arrow_vertical", ImageUtilities.loadImageIcon("org/openide/awt/resources/toolbar_arrow_vertical.png", false));
+            /* These are actually private classes in the openide.awt module. They must be copied in here if
+            they are to be shown with the utility. */
+            /*
+            ret.put("genvec_arrow", ArrowIcon.INSTANCE_DEFAULT);
+            ret.put("genvec_toolbar_arrow_horizontal", ToolbarArrowIcon.INSTANCE_HORIZONTAL);
+            ret.put("genvec_toolbar_arrow_vertical", ToolbarArrowIcon.INSTANCE_VERTICAL);
+             */
+        }
+        return Collections.unmodifiableMap(ret);
+    }
+
+    /**
+     * Utility method to add icons specific to the tabcontrol LAFs. Irrelevant when testing icons in
+     * other modules.
+     */
+    private static void addTabDisplayerIcons(
+            Map<String, Icon> toMap, String prefix, TabDisplayerUI tabDisplayerUI)
+    {
+        Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+        // ViewTabDisplayerUI
+        buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+        buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+        buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+        buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+        buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+        buttonIDs.put("restore_group", TabControlButton.ID_RESTORE_GROUP_BUTTON);
+        buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+        // EditorTabDisplayerUI
+        buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+        buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+        buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+        buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+        buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+
+        Map<String, Integer> buttonStates = new LinkedHashMap<String, Integer>();
+        buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+        buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+        buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+        buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+        for (Entry<String, Integer> buttonID : buttonIDs.entrySet()) {
+            for (Entry<String, Integer> buttonState : buttonStates.entrySet()) {
+                Icon icon = tabDisplayerUI.getButtonIcon(buttonID.getValue(), buttonState.getValue());
+                if (icon == null) {
+                    continue;
+                }
+                String key = prefix + "_" + buttonID.getKey() + "_" + buttonState.getKey();
+                Icon otherIcon = toMap.put(key, icon);
+                if (otherIcon != null && !otherIcon.equals(icon)) {
+                    throw new RuntimeException("Two related LAF classes both returned icons for key "
+                            + key + "; not sure which one to display");
+                }
+            }
+        }
+    }
+
+    public static void main(String args[]) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+                final VectorIconTester vit = new VectorIconTester();
+                vit.setVisible(true);
+                vit.validate();
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        vit.dumpGraphicsToFile();
+                    }
+                });
+            }
+        });
+    }
+
+    private static final class IconPreviewPane extends JPanel implements Scrollable {
+        private static final boolean INCLUDE_HUGE_ICON = true;
+        private static final int ICON_BASE_SIZE_X = TEST_AQUA ? 26 : 16;
+        private static final int ICON_BASE_SIZE_Y = 16;
+        private static final int ICON_ROW_HEIGHT
+                = Math.max(ICON_BASE_SIZE_Y * 3 + 8, (INCLUDE_HUGE_ICON ? 16 * 8 + 16 : 0));
+        private final Map<String, Map<String, Icon>> iconsByLAF;
+        private final Set<String> namesAfterLAF;
+        private int preferredWidth = 300;
+        /**
+         * If true, show the first entry in namesAfterLAF in the last timer-switched column,
+         * otherwise show the second.
+         */
+        private boolean timerSwitchState = false;
+        /**
+         * Continuously switch between the two first LAFs in the last column when Shift is held down
+         * (or toggled with space).
+         */
+        private final Timer lafSwitchTimer = new Timer(300, new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                switchLastColumnLAF();
+            }
+        });
+        /**
+         * Continuously repaint in case "Apply Code Changes" was applied in the debugger to modify
+         * the drawing routine.
+         */
+        private final Timer repaintTimer = new Timer(300, new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                repaint();
+            }
+        });
+
+        public void switchLastColumnLAF() {
+            timerSwitchState = !timerSwitchState;
+            repaint();
+        }
+
+        @Override
+        public Dimension getPreferredSize() {
+            return new Dimension(preferredWidth,
+                    namesAfterLAF.size() * ICON_ROW_HEIGHT + 2 * ICON_ROW_HEIGHT);
+        }
+
+        public IconPreviewPane(Map<String, Icon> icons) {
+            this.iconsByLAF = new LinkedHashMap<String, Map<String, Icon>>();
+            this.namesAfterLAF = new LinkedHashSet<String>();
+            for (Entry<String, Icon> iconEntry : icons.entrySet()) {
+                String name = iconEntry.getKey();
+                int pos = name.indexOf("_");
+                if (pos < 2) {
+                    throw new RuntimeException();
+                }
+                String lafPrefix = name.substring(0, pos);
+                Map<String, Icon> inLAFmap = iconsByLAF.get(lafPrefix);
+                if (inLAFmap == null) {
+                    inLAFmap = new LinkedHashMap<String, Icon>();
+                    iconsByLAF.put(lafPrefix, inLAFmap);
+                }
+                String nameAfterLAF = name.substring(pos + 1);
+                if (nameAfterLAF.isEmpty()) {
+                    throw new RuntimeException();
+                }
+                inLAFmap.put(nameAfterLAF, iconEntry.getValue());
+                namesAfterLAF.add(nameAfterLAF);
+                // Some of the mac icons are actually 26 pixels wide.
+                if (false) {
+                    Icon icon = iconEntry.getValue();
+                    if (icon.getIconWidth() > ICON_BASE_SIZE_X) {
+                        throw new RuntimeException();
+                    }
+                    if (icon.getIconHeight() > ICON_BASE_SIZE_Y) {
+                        throw new RuntimeException();
+                    }
+                }
+            }
+            this.lafSwitchTimer.setRepeats(true);
+            this.repaintTimer.setRepeats(true);
+            this.repaintTimer.start();
+            addKeyListener(new KeyAdapter() {
+                @Override
+                public void keyPressed(KeyEvent evt) {
+                    KeyStroke ks = KeyStroke.getKeyStrokeForEvent(evt);
+                    if (KeyStroke.getAWTKeyStroke("shift pressed SHIFT").equals(ks)) {
+                        if (!lafSwitchTimer.isRunning()) {
+                            switchLastColumnLAF();
+                            lafSwitchTimer.start();
+                        }
+                    } else if (KeyStroke.getAWTKeyStroke("pressed SPACE").equals(ks)) {
+                        if (lafSwitchTimer.isRunning()) {
+                            lafSwitchTimer.stop();
+                        } else {
+                            switchLastColumnLAF();
+                            lafSwitchTimer.start();
+                        }
+                    }
+                }
+
+                @Override
+                public void keyReleased(KeyEvent evt) {
+                    if (KeyStroke.getAWTKeyStroke("released SHIFT").equals(KeyStroke.getKeyStrokeForEvent(evt))) {
+                        lafSwitchTimer.stop();
+                    }
+                }
+            });
+            setFocusable(true);
+            requestFocusInWindow();
+        }
+
+        // This should really be a utility method somewhere...
+        // See VectorIcon.createGraphicsWithRenderingHintsConfigured.
+        private static Graphics2D createGraphicsWithRenderingHintsConfigured(Graphics basedOn) {
+            Graphics2D ret = (Graphics2D) basedOn.create();
+            Object desktopHints
+                    = Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
+            Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+            if (desktopHints != null && desktopHints instanceof Map<?, ?>) {
+                hints.putAll((Map<?, ?>) desktopHints);
+            }
+            hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+            ret.addRenderingHints(hints);
+            return ret;
+        }
+
+        @Override
+        protected void paintComponent(Graphics g) {
+            super.paintComponent(g);
+            Graphics2D g2 = createGraphicsWithRenderingHintsConfigured(g);
+            try {
+                paintComponent(g2, (Graphics2D) g);
+            } finally {
+                g2.dispose();
+            }
+        }
+
+        private void paintComponent(Graphics2D g, Graphics2D originalGraphics) {
+            List<String> columnsToShow = new ArrayList<String>(iconsByLAF.keySet());
+            if (columnsToShow.size() >= 2) {
+                columnsToShow.add(timerSwitchState ? columnsToShow.get(0) : columnsToShow.get(1));
+            }
+            Font font = new Font("Arial", Font.PLAIN, 12);
+            final int fontAscent = g.getFontMetrics(font).getAscent();
+            int x = 30;
+            final int START_Y = 30;
+            final int LAF_HEADING_Y_MARGIN = 30;
+            int y = START_Y;
+            g.setFont(font);
+            g.setColor(Color.BLACK);
+            // First column: name of button types.
+            y += LAF_HEADING_Y_MARGIN;
+            for (String nameAfterLAF : namesAfterLAF) {
+                g.drawString(nameAfterLAF, x, y + fontAscent);
+                y += ICON_ROW_HEIGHT;
+            }
+            x += 200;
+            int columnIndex = 0;
+            for (String lafName : columnsToShow) {
+                Map<String, Icon> lafIcons = iconsByLAF.get(lafName);
+                if (lafIcons == null) {
+                    throw new RuntimeException();
+                }
+                y = START_Y;
+                g.setFont(font);
+                g.setColor(Color.BLACK);
+                String columnTitle;
+                if (columnIndex == columnsToShow.size() - 1 && columnsToShow.size() > 1) {
+                    columnTitle = lafName + " (shift/space to toggle)";
+                } else {
+                    columnTitle = lafName;
+                }
+                g.drawString(columnTitle, x, y + fontAscent);
+                y += LAF_HEADING_Y_MARGIN;
+                int xAdvance = 0;
+                for (String nameAfterLAF : namesAfterLAF) {
+                    Icon icon = lafIcons.get(nameAfterLAF);
+                    if (icon != null) {
+                        /* Use the original graphics object here to make sure that icon painters set
+                        their own rendering hints as necessary. */
+                        xAdvance = Math.max(xAdvance, paintIconRow(g, originalGraphics, icon, x, y));
+                    }
+                    y += ICON_ROW_HEIGHT;
+                }
+                x += xAdvance;
+                columnIndex++;
+            }
+            if (x != preferredWidth) {
+                preferredWidth = x;
+                SwingUtilities.invokeLater(new Runnable() {
+                    @Override
+                    public void run() {
+                        revalidate();
+                    }
+                });
+            }
+        }
+
+        /**
+         * @return the X advance
+         */
+        private int paintIconRow(Graphics2D g, Graphics2D originalGraphics, Icon icon, int x, int y) {
+            // Show one column of icons in a different background, to test transparency.
+            g.setColor(Color.GREEN);
+            // Darker gray, for testing against darker LAF backgrounds.
+            //g.setColor(new Color(150, 150, 150, 255));
+            g.fillRect(x - 5, y - 5, ICON_BASE_SIZE_X + 10, ICON_ROW_HEIGHT + 10);
+            double useX = x;
+            int ret = 0;
+            for (int i = 0; i < 2; i++) {
+                if (!INCLUDE_HUGE_ICON && i == 1) {
+                    break; // Not enough space for a row simulating misalignment.
+                }
+                useX = x;
+                double useY = y + (i == 0 ? 0 : (16 * 4));
+                if (i == 1) {
+                    /* Simulate misalignment. Note that there won't really be misalignment at 100% scaling,
+                    since it's typically an artifact of non-integral HiDPI scaling. */
+                    useX += 0.49;
+                    useY += 0.49;
+                }
+                // Misalignment only needs to be simulated on non-integral HiDPI scalings.
+                useX += paintIcon(g, originalGraphics, i == 0 ? icon : null, useX, useY, 1.0);
+                useX += paintIcon(g, originalGraphics, i == 0 ? icon : null, useX, useY, 1.0);
+                useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.25);
+                useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.5);
+                useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.75);
+                useX += paintIcon(g, originalGraphics, i == 0 ? icon : null, useX, useY, 2.0);
+                useX += paintIcon(g, originalGraphics, icon, useX, useY, 2.25);
+                useX += paintIcon(g, originalGraphics, i == 0 ? icon : null, useX, useY, 3.0);
+                if (i == 0) {
+                    if (INCLUDE_HUGE_ICON) {
+                        useX += paintIcon(g, originalGraphics, icon, useX, y, 8.0);
+                    }
+                    ret = (int) (useX - x);
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * @param icon if null, don't paint, just return the advance
+         * @return the X advance
+         */
+        private int paintIcon(
+                Graphics2D newg, Graphics2D originalGraphics, Icon icon, double x, double y, double scaling)
+        {
+            String resstr;
+            boolean aligned = x == (int) x && y == (int) y;
+            if (icon == null) {
+                resstr = "";
+            } else if (!aligned) {
+                resstr = "misalign";
+            } else if (scaling > 1.0) {
+                resstr = ((int) Math.round(scaling * 100)) + "%";
+            } else {
+                resstr = icon.getIconWidth() + "x" + icon.getIconHeight();
+            }
+            newg.setFont(new Font("Arial", Font.PLAIN, 8));
+            newg.setColor(Color.BLACK);
+            if (!resstr.isEmpty()) {
+                newg.drawString(resstr, (int) x,
+                        ((int) y) - originalGraphics.getFontMetrics().getDescent());
+            }
+            AffineTransform oldTransform = originalGraphics.getTransform();
+            originalGraphics.translate(x, y);
+            originalGraphics.scale(scaling, scaling);
+            if (icon != null) {
+                // Make it evident if the icon forgets to set the color or shape.
+                originalGraphics.setColor(Color.PINK);
+                originalGraphics.setStroke(new BasicStroke((int) (10 * scaling)));
+                /* Paint the icon with a non-zero x/y offset, to make sure its implementation
+                handles this correctly. */
+                originalGraphics.translate(-10, -15);
+                icon.paintIcon(this, originalGraphics, 10, 15);
+                originalGraphics.translate(10, 15);
+            }
+            originalGraphics.setTransform(oldTransform);
+            if (icon != null && scaling > 4 && ((int) scaling) == scaling) {
+                int s = (int) scaling;
+                // Display a pixel grid on top
+                originalGraphics.setColor(new Color(0, 0, 0, 60));
+                originalGraphics.setStroke(new BasicStroke(1));
+                int w = icon.getIconWidth();
+                int h = icon.getIconHeight();
+                // Vertical lines.
+                for (int gridX = 0; gridX <= w; gridX++) {
+                    originalGraphics.drawLine(
+                            (int) x + gridX * s, (int) y, (int) x + gridX * s, (int) y + h * s);
+                }
+                // Horizontal lines.
+                for (int gridY = 0; gridY <= h; gridY++) {
+                    originalGraphics.drawLine(
+                            (int) x, (int) y + gridY * s, (int) x + w * s, (int) y + gridY * s);
+                }
+            }
+            return ICON_BASE_SIZE_X + (int) Math.ceil(scaling * ICON_BASE_SIZE_X);
+        }
+
+        @Override
+        public Dimension getPreferredScrollableViewportSize() {
+            return getPreferredSize();
+        }
+
+        @Override
+        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
+            return ICON_ROW_HEIGHT;
+        }
+
+        @Override
+        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
+            return ICON_ROW_HEIGHT;
+        }
+
+        @Override
+        public boolean getScrollableTracksViewportWidth() {
+            return false;
+        }
+
+        @Override
+        public boolean getScrollableTracksViewportHeight() {
+            return false;
+        }
+    }
+}
diff --git a/platform/openide.awt/nbproject/project.xml b/platform/openide.awt/nbproject/project.xml
index 7d008a9f94..0398c101d6 100644
--- a/platform/openide.awt/nbproject/project.xml
+++ b/platform/openide.awt/nbproject/project.xml
@@ -47,7 +47,7 @@
                     <build-prerequisite/>
                     <compile-dependency/>
                     <run-dependency>
-                        <specification-version>9.11</specification-version>
+                        <specification-version>9.12</specification-version>
                     </run-dependency>
                 </dependency>
                 <dependency>
diff --git a/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java b/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java
new file mode 100644
index 0000000000..4adfc32095
--- /dev/null
+++ b/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.awt;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import javax.swing.Icon;
+import org.openide.util.VectorIcon;
+
+/* For use by CloseButtonFactory only. The "mac_close_(enabled|pressed|rollover).png" files were
+confirmed to be identical to the mac_bigclose_(enabled|pressed|rollover).png ones, so the same
+vector icons can be used here for either case. */
+final class AquaVectorCloseButton extends VectorIcon {
+    public static final Icon DEFAULT = new AquaVectorCloseButton(State.DEFAULT);
+    public static final Icon PRESSED = new AquaVectorCloseButton(State.PRESSED);
+    public static final Icon ROLLOVER = new AquaVectorCloseButton(State.ROLLOVER);
+    private final State state;
+
+    private enum State { DEFAULT, PRESSED, ROLLOVER}
+
+    private AquaVectorCloseButton(State state) {
+        super(14, 12);
+        this.state = state;
+    }
+
+    @Override
+    protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+        /* Identical logic to that in o.n.swing.tabcontrol.plaf.AquaVectorTabControlIcon for the
+        TabControlButton.ID_CLOSE_BUTTON case. We can't depend on that module, however, and it makes
+        little sense for this module to expose a new API just to share this little piece of
+        platform-dependent code. */
+        double d = Math.min(width, height);
+        Color bgColor = new Color(0, 0, 0, 0);
+        Color fgColor = new Color(0, 0, 0, 168);
+        if (state == State.ROLLOVER) {
+            fgColor = Color.WHITE;
+            bgColor = new Color(255, 35, 25, 215);
+        } else if (state == State.PRESSED) {
+            fgColor = Color.WHITE;
+            bgColor = new Color(185, 43, 33, 215);
+        }
+        if (bgColor.getAlpha() > 0) {
+            double circPosX = (width - d) / 2.0;
+            double circPosY = (height - d) / 2.0;
+            Shape bgCircle = new Ellipse2D.Double(circPosX, circPosY, d, d);
+            g.setColor(bgColor);
+            g.fill(bgCircle);
+        }
+        g.setColor(fgColor);
+        double strokeWidth = 1.4 * scaling;
+        double mx = width / 2.0;
+        double my = height / 2.0;
+        double cr = 0.45 * (d / 2.0);
+        Stroke stroke = new BasicStroke(
+                (float) strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+        Area area = new Area();
+        area.add(new Area(stroke.createStrokedShape(
+                new Line2D.Double(mx - cr, my - cr, mx + cr, my + cr))));
+        area.add(new Area(stroke.createStrokedShape(
+                new Line2D.Double(mx + cr, my - cr, mx - cr, my + cr))));
+        g.fill(area);
+    }
+}
diff --git a/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java b/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
index 88278d0b73..b8b41bd5f0 100644
--- a/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
+++ b/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
@@ -149,7 +149,7 @@ private static Icon getCloseTabImage() {
         }
         if( null == closeTabImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                closeTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_enabled.png", true); // NOI18N
+                closeTabImage = Windows8VectorCloseButton.DEFAULT;
             } else if( isWindowsVistaLaF() ) {
                 closeTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_enabled.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -157,7 +157,7 @@ private static Icon getCloseTabImage() {
             } else if( isWindowsLaF() ) {
                 closeTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_enabled.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                closeTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_enabled.png", true); // NOI18N
+                closeTabImage = AquaVectorCloseButton.DEFAULT;
             } else if( isGTKLaF() ) {
                 closeTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_enabled.png", true); // NOI18N
             } else {
@@ -176,7 +176,7 @@ private static Icon getCloseTabPressedImage() {
         }
         if( null == closeTabPressedImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                closeTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_pressed.png", true); // NOI18N
+                closeTabPressedImage = Windows8VectorCloseButton.PRESSED;
             } else if( isWindowsVistaLaF() ) {
                 closeTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_pressed.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -184,7 +184,7 @@ private static Icon getCloseTabPressedImage() {
             } else if( isWindowsLaF() ) {
                 closeTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_pressed.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                closeTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_pressed.png", true); // NOI18N
+                closeTabPressedImage = AquaVectorCloseButton.PRESSED;
             } else if( isGTKLaF() ) {
                 closeTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_pressed.png", true); // NOI18N
             } else {
@@ -203,7 +203,7 @@ private static Icon getCloseTabRolloverImage() {
         }
         if( null == closeTabMouseOverImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                closeTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_rollover.png", true); // NOI18N
+                closeTabMouseOverImage = Windows8VectorCloseButton.PRESSED;
             } else if( isWindowsVistaLaF() ) {
                 closeTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_rollover.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -211,7 +211,7 @@ private static Icon getCloseTabRolloverImage() {
             } else if( isWindowsLaF() ) {
                 closeTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_rollover.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                closeTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_rollover.png", true); // NOI18N
+                closeTabMouseOverImage = AquaVectorCloseButton.ROLLOVER;
             } else if( isGTKLaF() ) {
                 closeTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_rollover.png", true); // NOI18N
             } else {
@@ -231,7 +231,7 @@ private static Icon getBigCloseTabImage() {
         }
         if( null == bigCloseTabImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                bigCloseTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_enabled.png", true); // NOI18N
+                bigCloseTabImage = Windows8VectorCloseButton.DEFAULT;
             } else if( isWindowsVistaLaF() ) {
                 bigCloseTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_enabled.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -239,7 +239,7 @@ private static Icon getBigCloseTabImage() {
             } else if( isWindowsLaF() ) {
                 bigCloseTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_enabled.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                bigCloseTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_enabled.png", true); // NOI18N
+                bigCloseTabImage = AquaVectorCloseButton.DEFAULT;
             } else if( isGTKLaF() ) {
                 bigCloseTabImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_enabled.png", true); // NOI18N
             } else {
@@ -258,7 +258,7 @@ private static  Icon getBigCloseTabPressedImage() {
         }
         if( null == bigCloseTabPressedImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                bigCloseTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_pressed.png", true); // NOI18N
+                bigCloseTabPressedImage = Windows8VectorCloseButton.PRESSED;
             } else if( isWindowsVistaLaF() ) {
                 bigCloseTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_pressed.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -266,7 +266,7 @@ private static  Icon getBigCloseTabPressedImage() {
             } else if( isWindowsLaF() ) {
                 bigCloseTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_pressed.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                bigCloseTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_pressed.png", true); // NOI18N
+                bigCloseTabPressedImage = AquaVectorCloseButton.PRESSED;
             } else if( isGTKLaF() ) {
                 bigCloseTabPressedImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_pressed.png", true); // NOI18N
             } else {
@@ -285,7 +285,7 @@ private static Icon getBigCloseTabRolloverImage() {
         }
         if( null == bigCloseTabMouseOverImage ) {
             if( isWindows8LaF() || isWindows10LaF() ) {
-                bigCloseTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_rollover.png", true); // NOI18N
+                bigCloseTabMouseOverImage = Windows8VectorCloseButton.PRESSED;
             } else if( isWindowsVistaLaF() ) {
                 bigCloseTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_rollover.png", true); // NOI18N
             } else if( isWindowsXPLaF() ) {
@@ -293,7 +293,7 @@ private static Icon getBigCloseTabRolloverImage() {
             } else if( isWindowsLaF() ) {
                 bigCloseTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_rollover.png", true); // NOI18N
             } else if( isAquaLaF() ) {
-                bigCloseTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_rollover.png", true); // NOI18N
+                bigCloseTabMouseOverImage = AquaVectorCloseButton.ROLLOVER;
             } else if( isGTKLaF() ) {
                 bigCloseTabMouseOverImage = ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_rollover.png", true); // NOI18N
             } else {
diff --git a/platform/openide.awt/src/org/openide/awt/DropDownButton.java b/platform/openide.awt/src/org/openide/awt/DropDownButton.java
index 8013b9327f..e2fc84d974 100644
--- a/platform/openide.awt/src/org/openide/awt/DropDownButton.java
+++ b/platform/openide.awt/src/org/openide/awt/DropDownButton.java
@@ -31,7 +31,6 @@
 import java.util.logging.Logger;
 import javax.swing.DefaultButtonModel;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JPopupMenu;
 import javax.swing.event.PopupMenuEvent;
@@ -218,7 +217,7 @@ private Icon _getRolloverIcon() {
             Icon orig = regIcons.get( ICON_ROLLOVER );
             if( null == orig )
                 orig = regIcons.get( ICON_NORMAL );
-            icon = new IconWithArrow( orig, !mouseInArrowArea );
+            icon = new IconWithArrow( orig, !mouseInArrowArea, false );
             arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER : ICON_ROLLOVER_LINE, icon );
         }
         return icon;
@@ -233,7 +232,7 @@ private Icon _getRolloverSelectedIcon() {
                 orig = regIcons.get( ICON_ROLLOVER );
             if( null == orig )
                 orig = regIcons.get( ICON_NORMAL );
-            icon = new IconWithArrow( orig, !mouseInArrowArea );
+            icon = new IconWithArrow( orig, !mouseInArrowArea, false );
             arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER_SELECTED : ICON_ROLLOVER_SELECTED_LINE, icon );
         }
         return icon;
@@ -274,7 +273,8 @@ private Icon updateIcons( Icon orig, String iconType ) {
             arrowIcons.remove( iconType );
         } else {
             regIcons.put( iconType, orig );
-            arrow = new ImageIcon(ImageUtilities.icon2Image(new IconWithArrow( orig, false )));
+            arrow = new IconWithArrow( orig, false,
+                iconType.equals(ICON_DISABLED) || iconType.equals(ICON_DISABLED_SELECTED));
             arrowIcons.put( iconType, arrow );
         }
         return arrow;
@@ -309,14 +309,12 @@ public void setRolloverSelectedIcon(Icon icon) {
 
     @Override
     public void setDisabledIcon(Icon icon) {
-        //TODO use 'disabled' arrow icon
         Icon arrow = updateIcons( icon, ICON_DISABLED );
         super.setDisabledIcon( hasPopupMenu() ? arrow : icon );
     }
 
     @Override
     public void setDisabledSelectedIcon(Icon icon) {
-        //TODO use 'disabled' arrow icon
         Icon arrow = updateIcons( icon, ICON_DISABLED_SELECTED );
         super.setDisabledSelectedIcon( hasPopupMenu() ? arrow : icon );
     }
diff --git a/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java b/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
index 7dec11a660..7e1b5e9cea 100644
--- a/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
+++ b/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
@@ -30,12 +30,10 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.swing.Icon;
-import javax.swing.ImageIcon;
 import javax.swing.JPopupMenu;
 import javax.swing.JToggleButton;
 import javax.swing.event.PopupMenuEvent;
 import javax.swing.event.PopupMenuListener;
-import org.openide.util.ImageUtilities;
 import org.openide.util.Parameters;
 
 /**
@@ -221,7 +219,7 @@ private Icon _getRolloverIcon() {
             Icon orig = regIcons.get( ICON_ROLLOVER );
             if( null == orig )
                 orig = regIcons.get( ICON_NORMAL );
-            icon = new IconWithArrow( orig, !mouseInArrowArea );
+            icon = new IconWithArrow( orig, !mouseInArrowArea, false );
             arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER : ICON_ROLLOVER_LINE, icon );
         }
         return icon;
@@ -236,7 +234,7 @@ private Icon _getRolloverSelectedIcon() {
                 orig = regIcons.get( ICON_ROLLOVER );
             if( null == orig )
                 orig = regIcons.get( ICON_NORMAL );
-            icon = new IconWithArrow( orig, !mouseInArrowArea );
+            icon = new IconWithArrow( orig, !mouseInArrowArea, false );
             arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER_SELECTED : ICON_ROLLOVER_SELECTED_LINE, icon );
         }
         return icon;
@@ -276,7 +274,8 @@ private Icon updateIcons( Icon orig, String iconType ) {
             arrowIcons.remove( iconType );
         } else {
             regIcons.put( iconType, orig );
-            arrow = new ImageIcon(ImageUtilities.icon2Image(new IconWithArrow( orig, false )));
+            arrow = new IconWithArrow( orig, false,
+                iconType.equals(ICON_DISABLED) || iconType.equals(ICON_DISABLED_SELECTED) );
             arrowIcons.put( iconType, arrow );
         }
         return arrow;
@@ -311,14 +310,12 @@ public void setRolloverSelectedIcon(Icon icon) {
 
     @Override
     public void setDisabledIcon(Icon icon) {
-        //TODO use 'disabled' arrow icon
         Icon arrow = updateIcons( icon, ICON_DISABLED );
         super.setDisabledIcon( hasPopupMenu() ? arrow : icon );
     }
 
     @Override
     public void setDisabledSelectedIcon(Icon icon) {
-        //TODO use 'disabled' arrow icon
         Icon arrow = updateIcons( icon, ICON_DISABLED_SELECTED );
         super.setDisabledSelectedIcon( hasPopupMenu() ? arrow : icon );
     }
diff --git a/platform/openide.awt/src/org/openide/awt/IconWithArrow.java b/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
index 2c9c029910..1c87da89b2 100644
--- a/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
+++ b/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
@@ -22,10 +22,12 @@
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
 import javax.swing.Icon;
 import javax.swing.UIManager;
-import org.openide.util.ImageUtilities;
 import org.openide.util.Parameters;
+import org.openide.util.VectorIcon;
 
 /**
  * An icon that paints a small arrow to the right of the provided icon.
@@ -34,20 +36,18 @@
  * @since 6.11
  */
 class IconWithArrow implements Icon {
-    
-    private static final String ARROW_IMAGE_NAME = "org/openide/awt/resources/arrow.png"; //NOI18N
-    
     private Icon orig;
-    private Icon arrow = ImageUtilities.loadImageIcon(ARROW_IMAGE_NAME, false);
+    private Icon arrow;
     private boolean paintRollOver;
     
     private static final int GAP = 6;
     
     /** Creates a new instance of IconWithArrow */
-    public IconWithArrow(  Icon orig, boolean paintRollOver ) {
+    public IconWithArrow(  Icon orig, boolean paintRollOver, boolean disabledArrow ) {
         Parameters.notNull("original icon", orig); //NOI18N
         this.orig = orig;
         this.paintRollOver = paintRollOver;
+        this.arrow = disabledArrow ? ArrowIcon.INSTANCE_DISABLED : ArrowIcon.INSTANCE_DEFAULT;
     }
 
     @Override
@@ -88,4 +88,31 @@ public int getIconHeight() {
     public static int getArrowAreaWidth() {
         return GAP/2 + 5;
     }
+
+    private static class ArrowIcon extends VectorIcon {
+        public static final Icon INSTANCE_DEFAULT = new ArrowIcon(false);
+        public static final Icon INSTANCE_DISABLED = new ArrowIcon(true);
+        private final boolean disabled;
+
+        private ArrowIcon(boolean disabled) {
+          super(5, 4);
+          this.disabled = disabled;
+        }
+
+        @Override
+        protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+            g.setColor(disabled ? new Color(201, 201, 201, 255) : new Color(86, 86, 86, 255));
+            final double overshoot = 2.0 / scaling;
+            final double arrowWidth = width + overshoot * scaling;
+            final double arrowHeight = height - 0.2 * scaling;
+            final double arrowMidX = arrowWidth / 2.0 - (overshoot / 2.0) * scaling;
+            g.clipRect(0, 0, width, height);
+            Path2D.Double arrowPath = new Path2D.Double();
+            arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, 0);
+            arrowPath.lineTo(arrowMidX, arrowHeight);
+            arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, 0);
+            arrowPath.closePath();
+            g.fill(arrowPath);
+        }
+    }
 }
diff --git a/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java b/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
index 4cad7bf229..b2c03dd728 100644
--- a/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
+++ b/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
@@ -19,9 +19,11 @@
 package org.openide.awt;
 
 import java.awt.AWTEvent;
+import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.Graphics2D;
 import java.awt.Insets;
 import java.awt.Toolkit;
 import java.awt.event.AWTEventListener;
@@ -30,14 +32,16 @@
 import java.awt.event.ComponentListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.awt.geom.Path2D;
 import javax.swing.BorderFactory;
+import javax.swing.Icon;
 import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JPopupMenu;
 import javax.swing.JToolBar;
 import javax.swing.UIManager;
-import org.openide.util.ImageUtilities;
 import org.openide.util.Mutex;
+import org.openide.util.VectorIcon;
 
 /**
  * ToolbarWithOverflow provides a component which is useful for displaying commonly used
@@ -53,8 +57,6 @@
     private JPopupMenu popup;
     private JToolBar overflowToolbar;
     private boolean displayOverflowOnHover = true;
-    private final String toolbarArrowHorizontal = "org/openide/awt/resources/toolbar_arrow_horizontal.png"; //NOI18N
-    private final String toolbarArrowVertical = "org/openide/awt/resources/toolbar_arrow_vertical.png"; //NOI18N
     private final String PROP_PREF_ICON_SIZE = "PreferredIconSize"; //NOI18N
     private final String PROP_DRAGGER = "_toolbar_dragger_"; //NOI18N
     private final String PROP_JDEV_DISABLE_OVERFLOW = "nb.toolbar.overflow.disable"; //NOI18N
@@ -279,7 +281,9 @@ public void validate() {
     }
     
     private void setupOverflowButton() {
-        overflowButton = new JButton(ImageUtilities.loadImageIcon(getOrientation() == HORIZONTAL ? toolbarArrowVertical : toolbarArrowHorizontal, false)) {
+        overflowButton = new JButton(getOrientation() == HORIZONTAL
+                ? ToolbarArrowIcon.INSTANCE_VERTICAL : ToolbarArrowIcon.INSTANCE_HORIZONTAL)
+        {
             @Override
             public void updateUI() {
                 Mutex.EVENT.readAccess(new Runnable() {
@@ -483,4 +487,45 @@ final void superUpdateUI() {
             super.updateUI();
         }
     }
+
+    /**
+     * Vectorized version of {@code toolbar_arrow_horizontal.png} and
+     * {@code toolbar_arrow_vertical.png}.
+     */
+    private static final class ToolbarArrowIcon extends VectorIcon {
+        public static final Icon INSTANCE_HORIZONTAL = new ToolbarArrowIcon(true);
+        public static final Icon INSTANCE_VERTICAL = new ToolbarArrowIcon(false);
+        private final boolean horizontal;
+
+        private ToolbarArrowIcon(boolean horizontal) {
+            super(11, 11);
+            this.horizontal = horizontal;
+        }
+
+        @Override
+        protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+            if (horizontal) {
+                // Rotate 90 degrees counterclockwise.
+                g.rotate(-Math.PI / 2.0, width / 2.0, height / 2.0);
+            }
+            // Draw two chevrons pointing downwards. Make strokes a little thicker at low scalings.
+            double strokeWidth = 0.8 * scaling + 0.3;
+            g.setStroke(new BasicStroke((float) strokeWidth));
+            g.setColor(new Color(50, 50, 50, 255));
+            for (int i = 0; i < 2; i++) {
+                final int y = round((1.4 + 4.1 * i) * scaling);
+                final double arrowWidth = round(5.0 * scaling);
+                final double arrowHeight = round(3.0 * scaling);
+                final double marginX = (width - arrowWidth) / 2.0;
+                final double arrowMidX = marginX + arrowWidth / 2.0;
+                // Clip the top of the chevrons.
+                g.clipRect(0, y, width, height);
+                Path2D.Double arrowPath = new Path2D.Double();
+                arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+                arrowPath.lineTo(arrowMidX, y + arrowHeight);
+                arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+                g.draw(arrowPath);
+            }
+        }
+    }
 }
diff --git a/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java b/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java
new file mode 100644
index 0000000000..ca2db83a88
--- /dev/null
+++ b/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.awt;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import javax.swing.Icon;
+import org.openide.util.VectorIcon;
+
+// For use by CloseButtonFactory only.
+final class Windows8VectorCloseButton extends VectorIcon {
+    public static final Icon DEFAULT = new Windows8VectorCloseButton(false);
+    public static final Icon PRESSED = new Windows8VectorCloseButton(true);
+    private final boolean pressed;
+
+    private Windows8VectorCloseButton(boolean pressed) {
+        super(14, 14);
+        this.pressed = pressed;
+    }
+
+    @Override
+    protected void paintIcon(Component c, Graphics2D g, int width, int height, double scaling) {
+        /* Identical logic to that in o.n.swing.tabcontrol.plaf.Windows8VectorTabControlIcon for the
+        TabControlButton.ID_CLOSE_BUTTON case. We can't depend on that module, however, and it makes
+        little sense for this module to expose a new API just to share this little piece of
+        platform-dependent code. */
+        if (pressed) {
+            g.setColor(new Color(199, 79, 80, 255));
+            g.fillRect(0, 0, width, height);
+        }
+        g.setColor(pressed ? Color.WHITE : new Color(86, 86, 86, 255));
+        if (getIconWidth() == width && getIconHeight() == height)
+            setAntiAliasing(g, false);
+        double strokeWidth = (pressed ? 1.0 : 0.8) * scaling;
+        if (scaling > 1.0)
+            strokeWidth *= 1.5f;
+        double marginX = 3.5 * scaling;
+        int topMarginY = round(3 * scaling);
+        int botMarginY = round(4 * scaling);
+        g.clip(new Rectangle2D.Double(0, topMarginY, width, height - topMarginY - botMarginY));
+        g.setStroke(new BasicStroke((float) strokeWidth));
+        g.draw(new Line2D.Double(marginX, topMarginY, width - marginX, height - botMarginY));
+        g.draw(new Line2D.Double(width - marginX, topMarginY, marginX, height - botMarginY));
+    }
+}
diff --git a/platform/openide.util.ui/apichanges.xml b/platform/openide.util.ui/apichanges.xml
index 50eeb6843c..465346ab0e 100644
--- a/platform/openide.util.ui/apichanges.xml
+++ b/platform/openide.util.ui/apichanges.xml
@@ -27,6 +27,32 @@
     <apidef name="actions">Actions API</apidef>
 </apidefs>
 <changes>
+    <change id="VectorIcon">
+      <api name="util"/>
+      <summary>Added abstract class VectorIcon to support creation of custom-painted HiDPI icons.</summary>
+      <version major="9" minor="12"/>
+      <date year="2018" month="9" day="29"/>
+      <author login="ebakke"/>
+      <compatibility addition="yes" binary="compatible" source="compatible" semantic="compatible"/>
+      <description>
+        <p>
+            It is now increasingly common for NetBeans to run on Windows, Linux, or MacOS machines with
+            so-called "HiDPI" screens, aka. "retina" screens in the Apple world. These screens have about
+            twice the physical pixel density of traditional screens, making it necessary to scale GUI
+            graphics up by some amount, e.g. 150% or 200% (depending on OS and OS-level user settings), in
+            order to remain readable. Since Java 9, this scaling is done automatically by AWT by means of a
+            scaling default transform in each Component's Graphics2D instances. This makes text sharp on
+            HiDPI screens, but leaves bitmap icons blurry.
+        </p>
+        <p>
+            This change introduces a new abstract class VectorIcon, which can be extended to create
+            custom-painted Icon instances that will look sharp on HiDPI screens, regardless of scaling level.
+            See VectorIcon's Javadoc for a discussion of appropriate use cases.
+        </p>
+      </description>
+      <class package="org.openide.util" name="VectorIcon"/>
+      <issue number="NETBEANS-1238"/>
+    </change>
     <change id="DeprecateBooleanStateAction">
         <api name="util"/>
         <summary><code>BooleanStateAction</code> deprecated in favour of <code>Actions</code> API and <code>@ActionState</code> annotation.</summary>
diff --git a/platform/openide.util.ui/manifest.mf b/platform/openide.util.ui/manifest.mf
index 4a6ac34359..9e63916b4e 100644
--- a/platform/openide.util.ui/manifest.mf
+++ b/platform/openide.util.ui/manifest.mf
@@ -1,5 +1,5 @@
 Manifest-Version: 1.0
 OpenIDE-Module: org.openide.util.ui
 OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
-OpenIDE-Module-Specification-Version: 9.11
+OpenIDE-Module-Specification-Version: 9.12
 
diff --git a/platform/openide.util.ui/src/org/openide/util/VectorIcon.java b/platform/openide.util.ui/src/org/openide/util/VectorIcon.java
new file mode 100644
index 0000000000..e961753b54
--- /dev/null
+++ b/platform/openide.util.ui/src/org/openide/util/VectorIcon.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.util;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.geom.AffineTransform;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.swing.Icon;
+
+/**
+ * A scalable icon that can be drawn at any resolution, for use with HiDPI displays. Implementations
+ * will typically use hand-crafted painting code that may take special care to align graphics to
+ * device pixels, and which may perform small tweaks to make the icon look good at all resolutions.
+ * The API of this class intends to make this straightforward.
+ *
+ * <p>HiDPI support now exists on MacOS, Windows, and Linux. On MacOS, scaling is 200% for Retina
+ * displays, while on Windows 10, the "Change display settings" panel provides the options 100%,
+ * 125%, 150%, 175%, 200%, and 225%, as well as the option to enter an arbitrary scaling factor.
+ * Non-integral scaling factors can lead to various alignment problems that makes otherwise
+ * well-aligned icons look unsharp; this class takes special care to avoid such problems.
+ *
+ * <p>Hand-crafted painting code is a good design choice for icons that are simple, ubiqutious in
+ * the UI (e.g. part of the Look-and-Feel), or highly parameterized. Swing's native Windows L&amp;F
+ * uses this approach for many of its basic icons; see
+ * {@link com.sun.java.swing.plaf.windows.WindowsIconFactory}.
+ *
+ * <p>When developing new icons, or adjusting existing ones, use the {@code VectorIconTester}
+ * utility found in
+ * {@code o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java}
+ * to preview and compare icons at different resolutions.
+ *
+ * @since 9.12
+ * @author Eirik Bakke
+ */
+public abstract class VectorIcon implements Icon, Serializable {    
+    private final int width;
+    private final int height;
+
+    protected VectorIcon(int width, int height) {
+        if (width < 0 || height < 0)
+            throw new IllegalArgumentException();
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public final int getIconWidth() {
+        return width;
+    }
+
+    @Override
+    public final int getIconHeight() {
+        return height;
+    }
+
+    private static Graphics2D createGraphicsWithRenderingHintsConfigured(Graphics basedOn) {
+        Graphics2D ret = (Graphics2D) basedOn.create();
+        Object desktopHints =
+                Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
+        Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+        if (desktopHints != null && desktopHints instanceof Map<?, ?>)
+            hints.putAll((Map<?, ?>) desktopHints);
+        /* Enable antialiasing by default. Adding this is required in order to get non-text
+        antialiasing on Windows. */
+        hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        /* In case a subclass decides to render text inside an icon, standardize the text
+        antialiasing setting as well. Don't try to follow the editor's anti-aliasing setting, or
+        to do subpixel rendering. It's more important that icons render in a predictable fashion, so
+        the icon designer can get can review the appearance at design time. */
+        hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        // Make stroke behavior as predictable as possible.
+        hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+        ret.addRenderingHints(hints);
+        return ret;
+    }
+
+    /**
+     * Selectively enable or disable antialiasing during painting. Certain shapes may look slightly
+     * better without antialiasing, e.g. entirely regular diagonal lines in very small icons when
+     * there is no HiDPI scaling. Text antialiasing is unaffected by this setting.
+     *
+     * @param g the graphics to set antialiasing setting for
+     * @param enabled whether antialiasing should be enabled or disabled
+     */
+    protected static final void setAntiAliasing(Graphics2D g, boolean enabled) {
+        Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+        hints.put(RenderingHints.KEY_ANTIALIASING, enabled
+                ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
+        g.addRenderingHints(hints);
+    }
+
+    protected static final int round(double d) {
+        int ret = (int) Math.round(d);
+        return d > 0 && ret == 0 ? 1 : ret;
+    }
+
+    @Override
+    public final void paintIcon(Component c, Graphics g0, int x, int y) {
+        final Graphics2D g2 = createGraphicsWithRenderingHintsConfigured(g0);
+        try {
+            // Make sure the subclass can't paint outside its stated dimensions.
+            g2.clipRect(x, y, getIconWidth(), getIconHeight());
+            g2.translate(x, y);
+            /**
+             * On HiDPI monitors, the Graphics object will have a default transform that maps
+             * logical pixels, like those you'd pass to Graphics.drawLine, to a higher number of
+             * device pixels on the screen. For instance, painting a line 10 pixels long on the
+             * current Graphics object would actually produce a line 20 device pixels long on a
+             * MacOS retina screen, which has a DPI scaling factor of 2.0. On Windows 10, many
+             * different scaling factors may be encountered, including non-integral ones such as
+             * 1.5. Detect the scaling factor here so we can use it to inform the drawing routines.
+             */
+            final double scaling;
+            final AffineTransform tx = g2.getTransform();
+            int txType = tx.getType();
+            if (txType == AffineTransform.TYPE_UNIFORM_SCALE ||
+                txType == (AffineTransform.TYPE_UNIFORM_SCALE | AffineTransform.TYPE_TRANSLATION))
+            {
+                scaling = tx.getScaleX();
+            } else {
+                // Unrecognized transform type. Don't do any custom scaling handling.
+                paintIcon(c, g2, getIconWidth(), getIconHeight(), 1.0);
+                return;
+            }
+            /* When using a non-integral scaling factor, such as 175%, preceding Swing components
+            often end up being a non-integral number of device pixels tall or wide. This will cause
+            our initial position to be "off the grid" with respect to device pixels, causing blurry
+            graphics even if we subsequently take care to use only integral numbers of device pixels
+            during painting. Fix this here by consuming a little bit of the top and left of the
+            icon's dimensions to offset any error. */
+            // The initial position, in device pixels.
+            final double previousDevicePosX = tx.getTranslateX();
+            final double previousDevicePosY = tx.getTranslateY();
+            /* The new, aligned position, after a small portion of the icon's dimensions may have
+            been consumed to correct it. */
+            final double alignedDevicePosX = Math.ceil(previousDevicePosX);
+            final double alignedDevicePosY = Math.ceil(previousDevicePosY);
+            // Use the aligned position.
+            g2.setTransform(new AffineTransform(
+                1, 0, 0, 1, alignedDevicePosX, alignedDevicePosY));
+            /* The portion of the icon's dimensions that was consumed to correct any initial
+            translation misalignment, in device pixels. May be zero. */
+            final double transDeviceAdjX = alignedDevicePosX - previousDevicePosX;
+            final double transDeviceAdjY = alignedDevicePosY - previousDevicePosY;
+            /* Now calculate the dimensions available for painting, also aligned to an integral
+            number of device pixels. */
+            final int deviceWidth  = (int) Math.floor(getIconWidth()  * scaling - transDeviceAdjX);
+            final int deviceHeight = (int) Math.floor(getIconHeight() * scaling - transDeviceAdjY);
+            paintIcon(c, g2, deviceWidth, deviceHeight, scaling);
+        } finally {
+            g2.dispose();
+        }
+    }
+
+    /**
+     * Paint the icon at the given width and height. The dimensions given are the device pixels onto
+     * which the icon must be drawn after it has been scaled up from its originally constant logical
+     * dimensions and aligned onto the device pixel grid. Painting onto the supplied
+     * {@code Graphics2D} instance using whole number coordinates (for horizontal and veritcal
+     * lines) will encourage sharp and well-aligned icons.
+     *
+     * <p>The icon should be painted with its upper left-hand corner at position (0, 0). Icons need
+     * not be opaque. Due to rounding errors and alignment correction, the aspect ratio of the
+     * device dimensions supplied here may not be exactly the same as that of the logical pixel
+     * dimensions specified in the constructor.
+     *
+     * @param c may be used to get properties useful for painting, as in
+     *        {@link Icon#paintIcon(Component,Graphics,int,int)}
+     * @param width the target width of the icon, after scaling and alignment adjustments, in device
+     *        pixels
+     * @param height the target height of the icon, after scaling and alignment adjustments, in
+     *        device pixels
+     * @param scaling the scaling factor that was used to scale the icon dimensions up to their
+     *        stated value
+     * @param g need <em>not</em> be cleaned up or restored to its previous state after use; will
+     *        have anti-aliasing already enabled by default
+     */
+    protected abstract void paintIcon(
+            Component c, Graphics2D g, int width, int height, double scaling);
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists