From 0da11ed70a73fa1392d42679869039b26d0e40aa Mon Sep 17 00:00:00 2001 From: weisj Date: Sat, 28 Mar 2020 21:51:51 +0100 Subject: [PATCH] Cleaned up PopupFactory implementation. Added flag to avoid painting the shadow border if the tooltip is in a focusable windows (i.e. is susceptible to repaints). New improved colour wheel implementation. ColorChooser now works more reliable when switching between colour modes. Added slimmer variant to the colour chooser. Added PopupColorChooser that utilises the slim colour chooser and sits inside a tooltip. --- .../weisj/darklaf/color/DarkColorModel.java | 29 +- .../darklaf/color/DarkColorModelCMYK.java | 17 +- .../darklaf/color/DarkColorModelHSB.java | 115 +-- .../darklaf/color/DarkColorModelHSL.java | 39 +- .../darklaf/color/DarkColorModelRGB.java | 69 ++ .../components/color/PopupColorChooser.java | 134 ++++ .../components/color/SmallColorChooser.java | 297 ++++++++ .../components/tooltip/ToolTipContext.java | 12 + .../decorators/UpdateDocumentListener.java | 43 ++ .../weisj/darklaf/ui/DarkPopupFactory.java | 42 +- .../colorchooser/ColorPreviewComponent.java | 10 +- .../ui/colorchooser/ColorTriangle.java | 678 ++++++++++++++++++ .../ui/colorchooser/ColorValueFormatter.java | 13 +- .../darklaf/ui/colorchooser/ColorWheel.java | 223 ------ .../colorchooser/ColorWheelImageProducer.java | 109 --- .../ui/colorchooser/ColorWheelPanel.java | 49 +- .../colorchooser/DarkColorChooserPanel.java | 379 +++++----- .../ui/colorchooser/DarkColorChooserUI.java | 11 +- .../ui/colorchooser/SlideComponent.java | 44 +- .../darklaf/ui/popupmenu/DarkPopupMenuUI.java | 7 + .../darklaf/ui/rootpane/DarkRootPaneUI.java | 1 - .../weisj/darklaf/ui/slider/DarkSliderUI.java | 2 +- .../darklaf/ui/tooltip/DarkTooltipBorder.java | 7 +- .../darklaf/ui/tooltip/DarkTooltipUI.java | 24 +- .../weisj/darklaf/ui/tooltip/ToolTipUtil.java | 14 +- .../weisj/darklaf/util/GraphicsContext.java | 32 +- .../properties/ui/colorChooser.properties | 3 + core/src/test/java/ui/QuickColorChooser.java | 28 +- .../java/ui/text/FormattedTextFieldDemo.java | 45 ++ core/src/test/java/ui/text/TextFieldDemo.java | 6 +- 30 files changed, 1730 insertions(+), 752 deletions(-) create mode 100644 core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelRGB.java create mode 100644 core/src/main/java/com/github/weisj/darklaf/components/color/PopupColorChooser.java create mode 100644 core/src/main/java/com/github/weisj/darklaf/components/color/SmallColorChooser.java create mode 100644 core/src/main/java/com/github/weisj/darklaf/decorators/UpdateDocumentListener.java create mode 100644 core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorTriangle.java delete mode 100644 core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java delete mode 100644 core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java create mode 100644 core/src/test/java/ui/text/FormattedTextFieldDemo.java diff --git a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java index 2651ab17..9e211193 100644 --- a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java +++ b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java @@ -29,17 +29,12 @@ import java.awt.*; /** * @author Jannis Weis */ -public class DarkColorModel { +public abstract class DarkColorModel { private final String prefix; private final String[] labels; - public DarkColorModel() { - this("rgb", "Red", "Green", "Blue"); - } - - public DarkColorModel(final String name, final String... labels) { this.prefix = "ColorChooser." + name; this.labels = labels; @@ -49,16 +44,12 @@ public class DarkColorModel { return this.labels.length; } - public int getMinimum(final int index) { - return 0; - } + public abstract int getMinimum(final int index); - public int getMaximum(final int index) { - return 255; - } + public abstract int getMaximum(final int index); public int getDefault(final int index) { - return 0; + return getMinimum(index); } public final String getText(final Component component, final String suffix) { @@ -66,16 +57,12 @@ public class DarkColorModel { } @Override - public String toString() { - return "RGB"; - } + public abstract String toString(); - public int getValueCount() { - return 3; - } + public abstract char[] getLabelDescriptorsBefore(); - public char[] getLabelDescriptorsBefore() { - return new char[]{'R', 'G', 'B'}; + public String[] getFullLabelDescriptorsBefore() { + return labels; } public char[] getLabelDescriptorsAfter() { diff --git a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelCMYK.java b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelCMYK.java index e8e5ee53..4d9634b1 100644 --- a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelCMYK.java +++ b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelCMYK.java @@ -33,10 +33,22 @@ public class DarkColorModelCMYK extends DarkColorModel { private static final int[] cmyk = new int[4]; private static final int[] rgb = new int[3]; + private static DarkColorModelCMYK instance; + + public static DarkColorModelCMYK getInstance() { + if (instance == null) instance = new DarkColorModelCMYK(); + return instance; + } + public DarkColorModelCMYK() { super("cmyk", "Cyan", "Magenta", "Yellow", "Black"); } + @Override + public int getMinimum(final int index) { + return 0; + } + @Override public int getMaximum(final int index) { return 100; @@ -47,11 +59,6 @@ public class DarkColorModelCMYK extends DarkColorModel { return "CMYK"; } - @Override - public int getValueCount() { - return 4; - } - @Override public char[] getLabelDescriptorsBefore() { return new char[]{'C', 'M', 'Y', 'K'}; diff --git a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java index 11e4dc88..7b6631bc 100644 --- a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java +++ b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java @@ -30,13 +30,25 @@ import java.awt.*; */ public class DarkColorModelHSB extends DarkColorModel { + private static final float[] hsvVals = new float[3]; private static final int[] hsb = new int[3]; - private static final int[] rgb = new int[3]; + + private static DarkColorModelHSB instance; + + public static DarkColorModelHSB getInstance() { + if (instance == null) instance = new DarkColorModelHSB(); + return instance; + } public DarkColorModelHSB() { super("hsv", "Hue", "Saturation", "Brightness"); } + @Override + public int getMinimum(final int index) { + return 0; + } + @Override public int getMaximum(final int index) { return (index == 0) ? 359 : 100; @@ -62,96 +74,25 @@ public class DarkColorModelHSB extends DarkColorModel { return RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue()); } - - public static int[] RGBtoHSB(final int r, final int g, final int b) { - double hue, saturation, brightness; - int cmax = Math.max(r, g); - if (b > cmax) cmax = b; - int cmin = Math.min(r, g); - if (b < cmin) cmin = b; - - brightness = ((double) cmax) / 255.0f; - if (cmax != 0) { - saturation = ((double) (cmax - cmin)) / ((double) cmax); - } else { - saturation = 0; - } - if (saturation == 0) { - hue = 0; - } else { - double redc = ((double) (cmax - r)) / ((double) (cmax - cmin)); - double greenc = ((double) (cmax - g)) / ((double) (cmax - cmin)); - double bluec = ((double) (cmax - b)) / ((double) (cmax - cmin)); - if (r == cmax) { - hue = bluec - greenc; - } else if (g == cmax) { - hue = 2.0f + redc - bluec; - } else { - hue = 4.0f + greenc - redc; - } - hue = hue / 6.0f; - if (hue < 0) { - hue = hue + 1.0f; - } - } - hsb[0] = (int) Math.round(hue * 360); - hsb[1] = (int) Math.round(saturation * 100); - hsb[1] = (int) Math.round(brightness * 100); + private static int[] RGBtoHSB(final int r, final int g, final int b) { + double[] values = RGBtoHSBValues(r, g, b); + hsb[0] = (int) Math.round(values[0] * 360); + hsb[1] = (int) Math.round(values[1] * 100); + hsb[1] = (int) Math.round(values[2] * 100); return hsb; } - @Override - public Color getColorFromValues(final int[] values) { - int[] rgb = HSBtoRGB(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0); - return new Color(rgb[0], rgb[1], rgb[2]); + public static double[] RGBtoHSBValues(final int r, final int g, final int b) { + float[] values = Color.RGBtoHSB(r, g, b, hsvVals); + return new double[]{values[0], values[1], values[2]}; + } + + public static Color getColorFromHSBValues(final double h, final double s, final double b) { + return Color.getHSBColor((float) h, (float) s, (float) b); } - public static int[] HSBtoRGB(final double hue, final double saturation, final double brightness) { - double r = 0, g = 0, b = 0; - if (saturation == 0) { - r = g = b = (brightness * 255.0f + 0.5f); - } else { - double h = (hue - Math.floor(hue)) * 6.0f; - double f = h - Math.floor(h); - double p = brightness * (1.0f - saturation); - double q = brightness * (1.0f - saturation * f); - double t = brightness * (1.0f - (saturation * (1.0f - f))); - switch ((int) Math.round(h)) { - case 0: - r = (brightness * 255.0f + 0.5f); - g = (t * 255.0f + 0.5f); - b = (p * 255.0f + 0.5f); - break; - case 1: - r = (q * 255.0f + 0.5f); - g = (brightness * 255.0f + 0.5f); - b = (p * 255.0f + 0.5f); - break; - case 2: - r = (p * 255.0f + 0.5f); - g = (brightness * 255.0f + 0.5f); - b = (t * 255.0f + 0.5f); - break; - case 3: - r = (p * 255.0f + 0.5f); - g = (q * 255.0f + 0.5f); - b = (brightness * 255.0f + 0.5f); - break; - case 4: - r = (t * 255.0f + 0.5f); - g = (p * 255.0f + 0.5f); - b = (brightness * 255.0f + 0.5f); - break; - case 5: - r = (brightness * 255.0f + 0.5f); - g = (p * 255.0f + 0.5f); - b = (q * 255.0f + 0.5f); - break; - } - } - rgb[0] = ((int) Math.round(r)); - rgb[1] = ((int) Math.round(g)); - rgb[2] = ((int) Math.round(b)); - return rgb; + @Override + public Color getColorFromValues(final int[] values) { + return getColorFromHSBValues(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0); } } diff --git a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSL.java b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSL.java index f8483ab2..d4400bdf 100644 --- a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSL.java +++ b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSL.java @@ -33,10 +33,22 @@ public class DarkColorModelHSL extends DarkColorModel { private static final int[] hsl = new int[3]; private static final int[] rgb = new int[3]; + private static DarkColorModelHSL instance; + + public static DarkColorModelHSL getInstance() { + if (instance == null) instance = new DarkColorModelHSL(); + return instance; + } + public DarkColorModelHSL() { super("hsl", "Hue", "Saturation", "Lightness"); } + @Override + public int getMinimum(final int index) { + return 0; + } + @Override public int getMaximum(final int index) { return (index == 0) ? 359 : 100; @@ -62,8 +74,12 @@ public class DarkColorModelHSL extends DarkColorModel { return RGBtoHSL(color.getRed(), color.getGreen(), color.getBlue()); } + public static Color getColorFromHSLValues(final double h, final double s, final double l) { + int[] rgb = HSLtoRGB(h, s, l); + return new Color(rgb[0], rgb[1], rgb[2]); + } - private static int[] RGBtoHSL(final int r, final int g, final int b) { + public static double[] RGBtoHSLValues(final int r, final int g, final int b) { double max = max(r, g, b) / 255.0; double min = min(r, g, b) / 255.0; @@ -74,12 +90,20 @@ public class DarkColorModelHSL extends DarkColorModel { ? 2.0f - summa : summa; } - hsl[0] = (int) Math.round(360 * getHue(r / 255.0, g / 255.0, b / 255.0, max, min)); - hsl[1] = (int) Math.round(100 * saturation); - hsl[2] = (int) Math.round(100 * (summa / 2.0)); - return hsl; + return new double[]{ + getHue(r / 255.0, g / 255.0, b / 255.0, max, min), + saturation, + summa / 2.0 + }; } + private static int[] RGBtoHSL(final int r, final int g, final int b) { + double[] values = RGBtoHSLValues(r, g, b); + hsl[0] = (int) Math.round(360 * values[0]); + hsl[1] = (int) Math.round(100 * values[1]); + hsl[2] = (int) Math.round(100 * values[2]); + return hsl; + } protected static double max(final double red, final double green, final double blue) { double max = Math.max(red, green); @@ -117,10 +141,10 @@ public class DarkColorModelHSL extends DarkColorModel { return new Color(rgb[0], rgb[1], rgb[2]); } - private static int[] HSLtoRGB(final double h, final double saturation, final double lightness) { double hue = h; - + while (hue < 0) hue += 1; + hue = hue - Math.floor(hue); if (saturation > 0.0f) { hue = (hue < 1.0f) ? hue * 6.0f : 0.0f; double q = lightness + saturation * ((lightness > 0.5f) ? 1.0f - lightness : lightness); @@ -136,7 +160,6 @@ public class DarkColorModelHSL extends DarkColorModel { return rgb; } - private static double normalize(final double q, final double p, final double color) { if (color < 1.0f) { return p + (q - p) * color; diff --git a/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelRGB.java b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelRGB.java new file mode 100644 index 00000000..1adff6e2 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelRGB.java @@ -0,0 +1,69 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.weisj.darklaf.color; + +import java.awt.*; + +public class DarkColorModelRGB extends DarkColorModel { + + private static DarkColorModelRGB instance; + + public static DarkColorModelRGB getInstance() { + if (instance == null) instance = new DarkColorModelRGB(); + return instance; + } + + public DarkColorModelRGB() { + super("rgb", "Red", "Green", "Blue"); + } + + public int getMinimum(final int index) { + return 0; + } + + public int getMaximum(final int index) { + return 255; + } + + @Override + public String toString() { + return "RGB"; + } + + public char[] getLabelDescriptorsBefore() { + return new char[]{'R', 'G', 'B'}; + } + + public char[] getLabelDescriptorsAfter() { + return new char[]{Character.MIN_VALUE, Character.MIN_VALUE, Character.MIN_VALUE, Character.MIN_VALUE}; + } + + public int[] getValuesFromColor(final Color color) { + return new int[]{color.getRed(), color.getGreen(), color.getBlue()}; + } + + public Color getColorFromValues(final int[] values) { + return new Color(values[0], values[1], values[2]); + } +} diff --git a/core/src/main/java/com/github/weisj/darklaf/components/color/PopupColorChooser.java b/core/src/main/java/com/github/weisj/darklaf/components/color/PopupColorChooser.java new file mode 100644 index 00000000..abb10cdd --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/components/color/PopupColorChooser.java @@ -0,0 +1,134 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.weisj.darklaf.components.color; + +import com.github.weisj.darklaf.components.tooltip.ToolTipContext; +import com.github.weisj.darklaf.ui.DarkPopupFactory; +import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder; +import com.github.weisj.darklaf.ui.tooltip.DarkTooltipUI; +import com.github.weisj.darklaf.util.Alignment; +import com.github.weisj.darklaf.util.DarkUIUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class PopupColorChooser extends JToolTip { + + protected DarkTooltipBorder border; + protected static SmallColorChooser chooser; + protected static ToolTipContext context; + + protected SmallColorChooser getChooser(final Color initial, final Consumer callback) { + if (chooser == null) chooser = new SmallColorChooser(initial, callback); + if (chooser.getParent() != null) { + // Already in use. Create new one. + return new SmallColorChooser(initial, callback); + } + chooser.reset(initial, callback); + return chooser; + } + + protected ToolTipContext getContext() { + if (context == null) context = createToolTipContext(); + return context; + } + + public PopupColorChooser(final JComponent parent, final Color initial, + final Consumer callback) { + setComponent(parent); + setLayout(new BorderLayout()); + add(getChooser(initial, callback), BorderLayout.CENTER); + putClientProperty(DarkPopupFactory.KEY_FOCUSABLE_POPUP, true); + putClientProperty(DarkTooltipUI.KEY_CONTEXT, getContext()); + } + + public static void showColorChooser(final JComponent parent, final Color initial, + final Consumer callback, + final Runnable onClose) { + JToolTip toolTip = new PopupColorChooser(parent, initial, callback); + final Popup popup = PopupFactory.getSharedInstance().getPopup(parent, toolTip, 0, 0); + popup.show(); + Window window = DarkUIUtil.getWindow(parent); + AtomicReference close = new AtomicReference<>(); + ComponentListener windowListener = new ComponentAdapter() { + @Override + public void componentMoved(final ComponentEvent e) { + close.get().run(); + } + + @Override + public void componentResized(final ComponentEvent e) { + close.get().run(); + } + }; + AWTEventListener listener = event -> { + if (!(DarkUIUtil.hasFocus(toolTip, (FocusEvent) event) || DarkUIUtil.hasFocus(toolTip))) { + close.get().run(); + } + }; + close.set(() -> { + popup.hide(); + Toolkit.getDefaultToolkit().removeAWTEventListener(listener); + if (window != null) window.removeComponentListener(windowListener); + if (onClose != null) onClose.run(); + }); + SwingUtilities.invokeLater(() -> { + window.addComponentListener(windowListener); + Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.FOCUS_EVENT_MASK); + }); + } + + protected ToolTipContext createToolTipContext() { + return new ToolTipContext() + .setAlignment(Alignment.CENTER) + .setCenterAlignment(Alignment.SOUTH) + .setUseBestFit(true) + .setFallBackPositionProvider(c -> { + Window window = DarkUIUtil.getWindow(c.getTarget()); + Dimension size = c.getToolTip().getPreferredSize(); + Rectangle bounds = window.getBounds(); + return new Point(bounds.x + (bounds.width - size.width) / 2, + bounds.y + (bounds.height - size.height) / 2); + }); + } + + @Override + public String getTipText() { + return ""; + } + + @Override + public Dimension getPreferredSize() { + if (isPreferredSizeSet()) { + return super.getPreferredSize(); + } + if (getLayout() != null) { + return getLayout().preferredLayoutSize(this); + } + return super.getPreferredSize(); + } +} diff --git a/core/src/main/java/com/github/weisj/darklaf/components/color/SmallColorChooser.java b/core/src/main/java/com/github/weisj/darklaf/components/color/SmallColorChooser.java new file mode 100644 index 00000000..65573205 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/components/color/SmallColorChooser.java @@ -0,0 +1,297 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.weisj.darklaf.components.color; + +import com.github.weisj.darklaf.color.DarkColorModel; +import com.github.weisj.darklaf.color.DarkColorModelHSB; +import com.github.weisj.darklaf.color.DarkColorModelHSL; +import com.github.weisj.darklaf.color.DarkColorModelRGB; +import com.github.weisj.darklaf.components.border.DarkBorders; +import com.github.weisj.darklaf.decorators.UpdateDocumentListener; +import com.github.weisj.darklaf.ui.colorchooser.ColorPreviewComponent; +import com.github.weisj.darklaf.ui.colorchooser.ColorTriangle; +import com.github.weisj.darklaf.ui.colorchooser.ColorValueFormatter; +import com.github.weisj.darklaf.ui.slider.DarkSliderUI; +import com.github.weisj.darklaf.ui.text.DarkTextUI; +import com.github.weisj.darklaf.util.ColorUtil; +import com.github.weisj.darklaf.util.DarkUIUtil; + +import javax.swing.*; +import java.awt.*; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class SmallColorChooser extends JPanel { + + private static final DarkColorModel[] COLOR_MODELS = new DarkColorModel[]{DarkColorModelRGB.getInstance(), + DarkColorModelHSB.getInstance(), + DarkColorModelHSL.getInstance()}; + + protected ColorTriangle colorTriangle; + protected ColorPreviewComponent previewComponent; + protected Color color; + private Consumer callback; + protected boolean valueChanging; + protected JTabbedPane colorModelTabbedPane; + protected JFormattedTextField hexField; + protected Map updateMap = new HashMap<>(); + protected ColorValueFormatter hexFormatter; + + public SmallColorChooser(final Color initial, final Consumer callback) { + this.color = initial; + setValueChanging(true); + setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 0; + constraints.gridwidth = 1; + constraints.gridheight = 1; + add(createTopComponent(), constraints); + constraints.gridy = 1; + add(createCenterComponent(), constraints); + constraints.gridy = 2; + add(createBottomComponent(), constraints); + + setValueChanging(false); + initListeners(); + reset(initial, callback); + } + + public void reset(final Color initial, final Consumer callback) { + this.callback = callback; + colorTriangle.setColor(this, initial); + } + + protected void initListeners() { + colorModelTabbedPane.addChangeListener(e -> { + hexFormatter.setModel(getDarkColorModel()); + colorTriangle.setColorModel(getDarkColorModel()); + }); + colorTriangle.addListener((c, o) -> { + previewComponent.setColor(c); + for (DarkColorModel model : COLOR_MODELS) { + if (o != model) updateMap.get(model).run(); + } + if (o != hexField) { + hexField.setText(ColorUtil.toHex(c)); + } + }); + hexField.getDocument().addDocumentListener((UpdateDocumentListener) () -> { + try { + String hexStr = String.format("%1$-" + 8 + "s", + hexField.getText()).replaceAll(" ", "F"); + int[] rgb = new int[]{Integer.valueOf(hexStr.substring(0, 2), 16), + Integer.valueOf(hexStr.substring(2, 4), 16), + Integer.valueOf(hexStr.substring(4, 6), 16)}; + setColor(hexField, DarkColorModelRGB.getInstance(), rgb); + } catch (NumberFormatException | IndexOutOfBoundsException ignore) { + } + }); + } + + protected void setColor(final DarkColorModel source, final int... values) { + setColor(source, source, values); + } + + protected void setColor(final Object source, final DarkColorModel model, final int... values) { + if (isValueChanging()) return; + setValueChanging(true); + if (model != null) { + colorTriangle.setColorFromModel(source, model, values); + } + if (callback != null) callback.accept(colorTriangle.getColor()); + setValueChanging(false); + } + + public boolean isValueChanging() { + return valueChanging; + } + + public void setValueChanging(final boolean valueChanging) { + this.valueChanging = valueChanging; + } + + protected JComponent createTopComponent() { + colorTriangle = createColorWheel(); + JPanel holder = new JPanel(new BorderLayout()); + holder.setOpaque(false); + holder.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + holder.add(colorTriangle, BorderLayout.CENTER); + return holder; + } + + protected JComponent createCenterComponent() { + colorModelTabbedPane = new JTabbedPane(); + colorModelTabbedPane.setOpaque(false); + colorModelTabbedPane.setBackground(DarkUIUtil.TRANSPARENT_COLOR); + addColorModels(colorModelTabbedPane, COLOR_MODELS); + colorModelTabbedPane.setBorder(DarkBorders.createLineBorder(1, 0, 1, 0)); + return colorModelTabbedPane; + } + + protected JComponent createBottomComponent() { + Box box = Box.createHorizontalBox(); + box.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + previewComponent = createPreviewComponent(); + + JPanel hexFieldHolder = new JPanel(new GridBagLayout()); + hexFieldHolder.setOpaque(false); + Box hexBox = Box.createHorizontalBox(); + hexBox.add(new JLabel("#")); + hexBox.add(createHexField()); + hexFieldHolder.add(hexBox); + + box.add(previewComponent); + box.add(Box.createGlue()); + box.add(hexFieldHolder); + box.add(Box.createGlue()); + box.add(Box.createHorizontalStrut(16)); + return box; + } + + protected JComponent createHexField() { + hexField = new JFormattedTextField(); + hexField.setColumns(6); + hexField.putClientProperty(DarkTextUI.KEY_ROUNDED_SELECTION, false); + hexField.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); + hexFormatter = ColorValueFormatter.init(null, 0, true, hexField); + hexFormatter.setModel(getDarkColorModel()); + return hexField; + } + + public Color getColor() { + return color; + } + + public DarkColorModel getDarkColorModel() { + return COLOR_MODELS[colorModelTabbedPane.getSelectedIndex()]; + } + + protected void addColorModels(final JTabbedPane tabbedPane, final DarkColorModel... colorModels) { + if (colorModels == null || colorModels.length == 0) { + throw new IllegalArgumentException("Must pass at least one valid colorModel"); + } + for (DarkColorModel model : colorModels) { + tabbedPane.addTab(model.toString(), createColorModelComponent(model)); + } + } + + protected JComponent createColorModelComponent(final DarkColorModel model) { + Box box = Box.createVerticalBox(); + box.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + String[] descriptors = model.getFullLabelDescriptorsBefore(); + char[] descriptorsAfter = model.getLabelDescriptorsAfter(); + int count = model.getCount(); + JSlider[] sliders = new JSlider[count]; + Descriptor[] labels = new Descriptor[count]; + for (int i = 0; i < count; i++) { + Descriptor label = new Descriptor(descriptors[i], + String.valueOf(descriptorsAfter[i])); + JSlider slider = new JSlider(model.getMinimum(i), model.getMaximum(i)); + slider.putClientProperty(DarkSliderUI.KEY_INSTANT_SCROLL, true); + slider.setValue(model.getDefault(i)); + slider.setSnapToTicks(false); + slider.setPaintLabels(false); + label.setLabelFor(slider); + + label.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + JPanel holder = new JPanel(new BorderLayout()); + holder.setOpaque(false); + holder.add(label, BorderLayout.BEFORE_FIRST_LINE); + holder.add(slider, BorderLayout.CENTER); + box.add(holder); + + sliders[i] = slider; + labels[i] = label; + + label.setValue(String.valueOf(slider.getValue())); + slider.addChangeListener(e -> { + if (isValueChanging()) return; + int[] values = new int[count]; + for (int j = 0; j < count; j++) { + values[j] = sliders[j].getValue(); + } + label.setValue(String.valueOf(slider.getValue())); + setColor(model, values); + }); + } + updateMap.put(model, () -> { + int[] values = colorTriangle.getValuesForModel(model); + for (int i = 0; i < count; i++) { + sliders[i].setValue(values[i]); + labels[i].setValue(String.valueOf(values[i])); + } + }); + return box; + } + + protected ColorTriangle createColorWheel() { + ColorTriangle wheel = new ColorTriangle(); + wheel.setMinimumSize(150); + wheel.setOpaque(false); + return wheel; + } + + protected ColorPreviewComponent createPreviewComponent() { + ColorPreviewComponent comp = new ColorPreviewComponent() { + @Override + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + @SuppressWarnings("SuspiciousNameCombination") + @Override + public Dimension getPreferredSize() { + Dimension size = hexField.getPreferredSize(); + size.width = size.height - 2 * UIManager.getInt("TextField.borderThickness"); + size.height = size.width; + return size; + } + }; + comp.setBorder(null); + return comp; + } + + protected static class Descriptor extends JLabel { + + protected final String before; + protected final String after; + protected String value; + + public Descriptor(final String before, final String after) { + this.before = before; + this.after = after; + setValue(null); + } + + public void setValue(final String value) { + this.value = value; + if (this.value == null) this.value = ""; + setText(before + ": " + value + after); + } + } +} diff --git a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java index 58a38af1..2dbde896 100644 --- a/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java +++ b/core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java @@ -80,6 +80,7 @@ public class ToolTipContext { private ToolTipStyle style; private boolean ignoreBorder; private boolean bestFit; + private Function fallBackPositionProvider; /** * Create a new tooltip context to ease the creation of custom tooltips. @@ -115,6 +116,7 @@ public class ToolTipContext { setToolTipStyle(ToolTipStyle.BALLOON); setUpdatePosition(false); setHideOnExit(false); + setFallBackPositionProvider(null); setAlignInside(alignInside); setAlignment(alignment); setCenterAlignment(centerAlignment); @@ -589,4 +591,14 @@ public class ToolTipContext { this.toolTip.setComponent(this.target); } } + + public Point getFallBackPosition() { + return fallBackPositionProvider.apply(this); + } + + public ToolTipContext setFallBackPositionProvider(final Function fallBackPositionProvider) { + this.fallBackPositionProvider = fallBackPositionProvider; + if (fallBackPositionProvider == null) this.fallBackPositionProvider = c -> null; + return this; + } } diff --git a/core/src/main/java/com/github/weisj/darklaf/decorators/UpdateDocumentListener.java b/core/src/main/java/com/github/weisj/darklaf/decorators/UpdateDocumentListener.java new file mode 100644 index 00000000..e56c1862 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/decorators/UpdateDocumentListener.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.weisj.darklaf.decorators; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public interface UpdateDocumentListener extends DocumentListener { + default void insertUpdate(final DocumentEvent e) { + update(); + } + + default void removeUpdate(final DocumentEvent e) { + update(); + } + + default void changedUpdate(final DocumentEvent e) { + update(); + } + + void update(); +} diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java b/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java index b3eb40c8..21f2ac3b 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java @@ -26,31 +26,37 @@ package com.github.weisj.darklaf.ui; import com.github.weisj.darklaf.platform.Decorations; import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI; import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI; -import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder; import javax.swing.*; import java.awt.*; public class DarkPopupFactory extends PopupFactory { + public static final String KEY_NO_DECORATION = "JPopupFactory.noDecorations"; + public static final String KEY_FOCUSABLE_POPUP = "JPopupFactory.focusablePopup"; + public static final String KEY_FORCE_HEAVYWEIGHT = "JPopupFactory.forceHeavyweight"; + public static final String KEY_START_HIDDEN = "JPopupFactory.startHidden"; + @Override public Popup getPopup(final Component owner, final Component contents, final int x, final int y) throws IllegalArgumentException { Popup popup = super.getPopup(owner, contents, x, y); boolean isMediumWeight = popup.getClass().getSimpleName().endsWith("MediumWeightPopup"); boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup"); - boolean isBalloonTooltip = contents instanceof JToolTip - && ((JToolTip) contents).getBorder() instanceof DarkTooltipBorder; - boolean isPopupMenu = contents instanceof JPopupMenu; - if (isMediumWeight || isLightWeight) { - if (isBalloonTooltip) { - // null owner forces a heavyweight popup. - popup = super.getPopup(null, contents, x, y); - } else if (isMediumWeight) { - JRootPane rootPane = SwingUtilities.getRootPane(contents); - // Prevents decorations from being reinstalled. - if (rootPane != null) rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT, true); - } + boolean forceHeavy = contents instanceof JComponent + && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_FORCE_HEAVYWEIGHT)); + boolean isFocusable = contents instanceof JComponent + && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_FOCUSABLE_POPUP)); + boolean startHidden = contents instanceof JComponent + && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_START_HIDDEN)); + if (forceHeavy && (isMediumWeight || isLightWeight)) { + // null owner forces a heavyweight popup. + popup = super.getPopup(null, contents, x, y); + } + if (isMediumWeight) { + JRootPane rootPane = SwingUtilities.getRootPane(contents); + // Prevents decorations from being reinstalled. + if (rootPane != null) rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT, true); } // Sometimes the background is java.awt.SystemColor[i=7] // It results in a flash of white background, that is repainted with @@ -62,17 +68,21 @@ public class DarkPopupFactory extends PopupFactory { if (window instanceof RootPaneContainer) { JRootPane rootPane = ((RootPaneContainer) window).getRootPane(); rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_POPUP, true); - install = !Boolean.TRUE.equals(rootPane.getClientProperty(DarkRootPaneUI.KEY_IS_TOOLTIP)); + install = !Boolean.TRUE.equals(rootPane.getClientProperty(KEY_NO_DECORATION)); } if (install) { Decorations.installPopupWindow(window); } else { Decorations.uninstallPopupWindow(window); } - if (isBalloonTooltip || isPopupMenu) { - if (isPopupMenu) ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true); + if (startHidden) { + ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true); window.setOpacity(0.0f); } + if (isFocusable && !window.getFocusableWindowState()) { + window.dispose(); + window.setFocusableWindowState(true); + } } return popup; } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java index 1de6744c..bb0bcad8 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java @@ -29,17 +29,17 @@ import java.awt.*; /** * @author Jannis Weis */ -final class ColorPreviewComponent extends JComponent { +public class ColorPreviewComponent extends JComponent { protected final Color borderColor; - private Color myColor; + private Color color; - ColorPreviewComponent() { + public ColorPreviewComponent() { setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); borderColor = UIManager.getColor("ColorChooser.previewBorderColor"); } public void setColor(final Color c) { - myColor = c; + color = c; repaint(); } @@ -54,7 +54,7 @@ final class ColorPreviewComponent extends JComponent { g.setColor(Color.WHITE); g.fillRect(i.left, i.top, width, height); - g.setColor(myColor); + g.setColor(color); g.fillRect(i.left + 1, i.top + 1, width - 2, height - 2); g.setColor(borderColor); diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorTriangle.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorTriangle.java new file mode 100644 index 00000000..499058a7 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorTriangle.java @@ -0,0 +1,678 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.weisj.darklaf.ui.colorchooser; + + +import com.github.weisj.darklaf.color.DarkColorModel; +import com.github.weisj.darklaf.color.DarkColorModelHSB; +import com.github.weisj.darklaf.color.DarkColorModelHSL; +import com.github.weisj.darklaf.color.DarkColorModelRGB; +import com.github.weisj.darklaf.util.ColorUtil; +import com.github.weisj.darklaf.util.GraphicsContext; +import com.github.weisj.darklaf.util.GraphicsUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.*; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.util.ArrayList; +import java.util.List; + +public class ColorTriangle extends JComponent { + + protected static final double SQRT3 = Math.sqrt(3); + private static final Point2D dummy = new Point2D.Double(); + protected static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); + + protected final List myListeners = new ArrayList<>(); + protected Color dropFill; + protected Color dropBorder; + protected Color background; + protected int outerIndicatorRadius; + protected int innerIndicatorRadius; + + protected Color color; + protected double hueHSB; + protected double hueHSL; + protected double valueHSB; + protected double saturationHSB; + protected double saturationHSL; + protected double lightnessHSL; + protected double opacity = 1.0; + + protected Shape circleShape; + protected Shape triangleShape; + protected AffineTransform triangleInverse; + protected Shape outerIndicator; + protected Shape innerIndicator; + + protected double centerX; + protected double centerY; + protected double outerRadius; + protected double innerRadius; + protected double rotation; + + protected PickResult lastPick; + protected int minSize = 300; + + protected boolean isMessaging; + protected boolean invalid; + private boolean isHSB = true; + + + public ColorTriangle() { + setOpaque(true); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(final ComponentEvent e) { + invalid = true; + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + setFromPickResult(pick(e.getX(), e.getY()), true); + } + + @Override + public void mouseReleased(final MouseEvent e) { + lastPick = null; + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(final MouseEvent e) { + if (lastPick != null && lastPick.area != PickArea.OUTSIDE) { + setFromPickResult(pick(e.getX(), e.getY()), false); + } + } + }); + updateDefaults(); + } + + protected void updateDefaults() { + background = UIManager.getColor("ColorChooser.colorWheelBackground"); + dropFill = UIManager.getColor("ColorChooser.colorWheelDropBackgroundColor"); + dropBorder = UIManager.getColor("ColorChooser.colorWheelDropBorderColor"); + outerIndicatorRadius = UIManager.getInt("ColorChooser.outerIndicatorRadius"); + innerIndicatorRadius = UIManager.getInt("ColorChooser.innerIndicatorRadius"); + } + + protected double getHue() { + return isHSB ? getHSBHue() : getHSLHue(); + } + + protected double getHSBHue() { + return hueHSB; + } + + protected double getHSLHue() { + return hueHSL; + } + + protected double getSaturation() { + return isHSB ? getHSBSaturation() : getHSLSaturation(); + } + + protected double getHSBSaturation() { + return saturationHSB; + } + + protected double getHSLSaturation() { + return saturationHSL; + } + + protected double getValue() { + return isHSB ? getHSBValue() : getHSLValue(); + } + + protected double getHSBValue() { + return valueHSB; + } + + protected double getHSLValue() { + return lightnessHSL; + } + + protected void setHue(final double hue) { + if (isHSB) { + setHSBHue(hue); + } else { + setHSLHue(hue); + } + } + + protected void setHSBHue(final double hue) { + hueHSB = hue; + } + + protected void setHSLHue(final double hue) { + hueHSL = hue; + } + + protected void setValue(final double value) { + if (isHSB) { + setHSBValue(value); + } else { + setHSLValue(value); + } + } + + protected void setHSBValue(final double value) { + valueHSB = value; + } + + protected void setHSLValue(final double value) { + lightnessHSL = value; + } + + protected void setSaturation(final double saturation) { + if (isHSB) { + setHSBSaturation(saturation); + } else { + setHSLSaturation(saturation); + } + } + + protected void setHSBSaturation(final double saturation) { + saturationHSB = saturation; + } + + protected void setHSLSaturation(final double saturation) { + saturationHSL = saturation; + } + + protected void setFromPickResult(final PickResult res, final boolean updateLastPick) { + if (updateLastPick) { + lastPick = res; + } + if (lastPick != null) { + if (lastPick.area == PickArea.WHEEL) { + setHue(res.hue); + rotation = res.rotation; + syncValues(); + invalidateWheel(); + fireColorChanged(this); + } else if (lastPick.area == PickArea.TRIANGLE) { + setValue(res.value); + setSaturation(res.saturation); + syncValues(); + invalidateWheel(); + fireColorChanged(this); + } + } + } + + protected void setHSB(final double[] hsb) { + setHSB(hsb[0], hsb[1], hsb[2]); + } + + protected void setHSB(final double h, final double s, final double b) { + setHue(h); + setHSBSaturation(s); + setHSBValue(b); + } + + protected void setHSL(final double[] hsl) { + setHSL(hsl[0], hsl[1], hsl[2]); + } + + protected void setHSL(final double h, final double s, final double l) { + setHue(h); + setHSLSaturation(s); + setHSLValue(l); + } + + protected int getColorRGB(final double h, final double s, final double v) { + if (isHSB) { + return Color.HSBtoRGB((float) h, (float) s, (float) v); + } else { + return DarkColorModelHSL.getColorFromHSLValues(h, s, v).getRGB(); + } + } + + public double getOpacity() { + return opacity; + } + + public void setOpacity(final double opacity) { + this.opacity = opacity; + } + + public int[] getValuesForModel(final DarkColorModel model) { + if (model instanceof DarkColorModelHSB) { + return new int[]{(int) Math.round(getHue() * model.getMaximum(0)), + (int) Math.round(getHSBSaturation() * model.getMaximum(1)), + (int) Math.round(getHSBValue() * model.getMaximum(2))}; + } else if (model instanceof DarkColorModelHSL) { + return new int[]{(int) Math.round(getHue() * model.getMaximum(0)), + (int) Math.round(getHSLSaturation() * model.getMaximum(1)), + (int) Math.round(getHSLValue() * model.getMaximum(2))}; + } else { + return model.getValuesFromColor(color); + } + } + + public void setColorFromModel(final Object source, final DarkColorModel model, final int[] values) { + double x = values[0] / (double) model.getMaximum(0); + double y = values[1] / (double) model.getMaximum(1); + double z = values[2] / (double) model.getMaximum(2); + if (model instanceof DarkColorModelHSB) { + setColorFromHSB(source, x, y, z); + } else if (model instanceof DarkColorModelHSL) { + setColorFromHSL(source, x, y, z); + } else if (model instanceof DarkColorModelRGB) { + setColorFromRGB(source, values[0], values[1], values[2]); + } else { + setColor(source, model.getColorFromValues(values)); + } + } + + public void setColor(final Object source, final Color color) { + setColorFromRGB(source, color.getRed(), color.getBlue(), color.getGreen()); + } + + protected void setColorFromRGB(final Object source, final int r, final int g, final int b) { + if (isMessaging) return; + this.color = new Color(r, g, b); + double[] hsb = DarkColorModelHSB.RGBtoHSBValues(r, g, b); + double[] hsl = DarkColorModelHSL.RGBtoHSLValues(r, g, b); + if (Color.getHSBColor((float) getHSBHue(), (float) hsb[1], (float) hsb[2]).equals(color)) { + hsb[0] = getHSBHue(); + hsl[0] = getHSLHue(); + } + setHSL(hsl); + setHSB(hsb); + rotation = -(getHue() * 2 * Math.PI) + Math.PI / 2; + invalidateWheel(); + fireColorChanged(source); + } + + protected void setColorFromHSL(final Object source, final double h, final double s, final double l) { + if (isMessaging) return; + setHSL(h, s, l); + color = DarkColorModelHSL.getColorFromHSLValues(h, s, l); + setHSB(DarkColorModelHSB.RGBtoHSBValues(color.getRed(), color.getGreen(), color.getBlue())); + rotation = -(getHue() * 2 * Math.PI) + Math.PI / 2; + invalidateWheel(); + fireColorChanged(source); + } + + protected void setColorFromHSB(final Object source, final double h, final double s, final double b) { + if (isMessaging) return; + setHSB(h, s, b); + color = DarkColorModelHSB.getColorFromHSBValues(h, s, b); + setHSL(DarkColorModelHSL.RGBtoHSLValues(color.getRed(), color.getGreen(), color.getBlue())); + rotation = -(getHue() * 2 * Math.PI) + Math.PI / 2; + invalidateWheel(); + fireColorChanged(source); + } + + protected void invalidateWheel() { + invalid = true; + Component parent = getParent(); + if (parent != null) { + parent.repaint(); + } else { + repaint(); + } + } + + protected void syncValues() { + if (isHSB) { + color = Color.getHSBColor((float) getHSBHue(), (float) getHSBSaturation(), (float) getHSBValue()); + setHSL(DarkColorModelHSL.RGBtoHSLValues(color.getRed(), color.getGreen(), color.getBlue())); + } else { + color = DarkColorModelHSL.getColorFromHSLValues(getHSLHue(), getHSLSaturation(), getHSLValue()); + setHSB(DarkColorModelHSB.RGBtoHSBValues(color.getRed(), color.getGreen(), color.getBlue())); + } + } + + protected void fireColorChanged(final Object source) { + isMessaging = true; + Color notifyColor = ColorUtil.toAlpha(color, opacity); + for (ColorListener listener : myListeners) { + listener.colorChanged(notifyColor, source); + } + isMessaging = false; + } + + @Override + public void updateUI() { + super.updateUI(); + updateDefaults(); + } + + protected PickResult pick(final double x, final double y) { + Point2D p = dummy; + p.setLocation(x, y); + double rotation = getRotation(x, y, centerX, centerY); + double hue = 0.25 - rotation / (2.0 * Math.PI); + triangleInverse.transform(p, p); + Point2D sv = getSaturationAndValue(p.getX(), p.getY()); + PickArea area = PickArea.OUTSIDE; + if (triangleShape.contains(x, y) || innerIndicator.contains(x, y)) { + area = PickArea.TRIANGLE; + } else if (circleShape.contains(x, y) || outerIndicator.contains(x, y)) { + area = PickArea.WHEEL; + } + return new PickResult(area, rotation, hue, sv.getX(), sv.getY()); + } + + protected Point2D getSaturationAndValue(final double x, final double y) { + double x1 = (x - centerX) / innerRadius; + double y1 = (y - centerY) / innerRadius; + double sat = (1.0 - 2.0 * y1) / (SQRT3 * x1 - y1 + 2.0); + double val = (SQRT3 * x1 - y1 + 2.0) / 3.0; + return new Point2D.Double(Math.max(Math.min(sat, 1), 0), Math.max(Math.min(val, 1), 0)); + } + + protected static double getRotation(final double x, final double y, final double cx, final double cy) { + return Math.PI - Math.atan2(x - cx, y - cy); + } + + protected static double getWheelHue(final double x, final double y, final double cx, final double cy) { + return (Math.atan2(x - cx, y - cy) - Math.PI / 2.0) / (2 * Math.PI); + } + + @Override + public void paint(final Graphics g) { + GraphicsContext context = GraphicsUtil.setupAAPainting(g); + Graphics2D g2d = (Graphics2D) g; + + final Dimension dim = getSize(); + int size = Math.min(dim.width, dim.height); + size = Math.min(size, 600); + float x = (float) ((dim.width - size) / 2.0); + float y = (float) ((dim.height - size) / 2.0); + centerX = x + size / 2.0; + centerY = y + size / 2.0; + + if (invalid || circleShape == null || triangleShape == null || outerIndicator == null) { + createShapes(x, y, size); + } + + g2d.setComposite(COMPOSITE.derive((float) opacity)); + g2d.setPaint(new InnerPaint()); + g2d.fill(triangleShape); + + g2d.setPaint(new OuterPaint()); + g2d.fill(circleShape); + context.restoreComposite(); + + drawIndicator(g2d, outerIndicator); + drawIndicator(g2d, innerIndicator); + } + + public void createShapes(final float x, final float y, final int size) { + int outerSize = 15; + AffineTransform rotationTransform = AffineTransform.getRotateInstance(rotation, centerX, centerY); + + circleShape = calculateCircleShape(x, y, size, outerSize); + triangleShape = calculateTriangleShape(x, y, size, outerSize, rotationTransform); + outerIndicator = createOuterIndicator(centerX, centerY, (innerRadius + outerRadius) / 2.0, + rotationTransform, outerIndicatorRadius); + innerIndicator = createInnerIndicator(rotationTransform, innerIndicatorRadius); + + invalid = false; + try { + // For calculating pick location. MouseEvents already respect the ui scaling. + triangleInverse = rotationTransform.createInverse(); + } catch (NoninvertibleTransformException e) { + e.printStackTrace(); + } + } + + protected void drawIndicator(final Graphics2D g, final Shape indicator) { + Stroke old = g.getStroke(); + + g.setStroke(new BasicStroke(3)); + g.setColor(dropBorder); + g.draw(indicator); + + g.setStroke(old); + g.setColor(dropFill); + g.draw(indicator); + } + + protected Shape createInnerIndicator(final AffineTransform transform, final int dotRadius) { + Point2D p = getTrianglePos(getSaturation(), getValue()); + transform.transform(p, p); + return createIndicatorShape(p, dotRadius); + } + + protected Shape createOuterIndicator(final double cx, final double cy, final double radius, + final AffineTransform transform, final int dotRadius) { + dummy.setLocation(cx, cy - radius); + transform.transform(dummy, dummy); + return createIndicatorShape(dummy, dotRadius); + } + + protected Shape createIndicatorShape(final Point2D p, final int radius) { + return new Ellipse2D.Double(p.getX() - radius, p.getY() - radius, 2 * radius, 2 * radius); + } + + protected Shape calculateTriangleShape(final double x, final double y, final int size, final int outerSize, + final AffineTransform transform) { + double diameter = (size - 2 * outerSize); + double radius = diameter / 2.0; + double sideLength = Math.cos(Math.PI / 6.0) * diameter; + double height = (SQRT3 / 2.0) * sideLength; + + double upperX = radius + x + outerSize; + double upperY = y + outerSize; + + Path2D trianglePath = new Path2D.Float(Path2D.WIND_EVEN_ODD); + trianglePath.moveTo(upperX, upperY); + trianglePath.lineTo(upperX - sideLength / 2.0, upperY + height); + trianglePath.lineTo(upperX + sideLength / 2.0, upperY + height); + trianglePath.closePath(); + trianglePath.transform(transform); + return trianglePath; + } + + protected Shape calculateCircleShape(final double x, final double y, final int size, final int outerSize) { + outerRadius = size / 2.0; + innerRadius = outerRadius - outerSize; + + Area outer = new Area(new Ellipse2D.Double(x, y, size, size)); + Area inner = new Area(new Ellipse2D.Double(x + outerSize, y + outerSize, + size - 2 * outerSize, + size - 2 * outerSize)); + outer.subtract(inner); + return outer; + } + + protected Point2D getTrianglePos(final double sat, final double val) { + return new Point2D.Double(centerX + innerRadius * (2 * val - sat * val - 1) * SQRT3 / 2.0, + centerY + innerRadius * (1 - 3 * sat * val) / 2.0); + + } + + + @Override + public Dimension getPreferredSize() { + return getMinimumSize(); + } + + @Override + public Dimension getMinimumSize() { + return new Dimension(minSize, minSize); + } + + public void setMinimumSize(final int size) { + minSize = size; + } + + public void addListener(final ColorListener listener) { + myListeners.add(listener); + } + + public Color getColor() { + return ColorUtil.toAlpha(color, opacity); + } + + public void setColorModel(final DarkColorModel model) { + boolean hsb = !(model instanceof DarkColorModelHSL); + if (isHSB != hsb) { + isHSB = hsb; + invalidateWheel(); + } + } + + public abstract static class ColorWheelPaint implements Paint { + + protected final ColorTriangle.ColorWheelPaintContext context; + + protected ColorWheelPaint(final ColorTriangle.ColorWheelPaintContext context) { + this.context = context; + } + + @Override + public PaintContext createContext(final ColorModel cm, final Rectangle deviceBounds, + final Rectangle2D userBounds, final AffineTransform xform, + final RenderingHints hints) { + context.setHints(deviceBounds, xform); + return context; + } + + @Override + public int getTransparency() { + return Transparency.OPAQUE; + } + } + + protected class InnerPaint extends ColorWheelPaint { + + protected InnerPaint() { + super(new InnerPaintContext()); + } + } + + protected static class OuterPaint extends ColorWheelPaint { + protected OuterPaint() { + super(new ColorTriangle.OuterPaintContext()); + } + } + + protected static abstract class ColorWheelPaintContext implements PaintContext { + protected Rectangle deviceBounds; + protected double cx; + protected double cy; + protected AffineTransform transform; + + public void setHints(final Rectangle deviceBounds, final AffineTransform transform) { + this.deviceBounds = deviceBounds; + cx = deviceBounds.x + deviceBounds.width / 2.0; + cy = deviceBounds.y + deviceBounds.height / 2.0; + this.transform = transform; + } + + @Override + public void dispose() { + } + + @Override + public ColorModel getColorModel() { + return ColorModel.getRGBdefault(); + } + + protected void setPixel(final WritableRaster raster, final int i, final int j, final int rgb) { + setPixel(raster, i, j, new int[]{(rgb >> 16) & 0xFF, + (rgb >> 8) & 0xFF, + (rgb) & 0xFF, + 255}); + } + + protected void setPixel(final WritableRaster raster, final int i, final int j, final int[] vals) { + raster.setPixel(i, j, vals); + } + } + + protected static class OuterPaintContext extends ColorWheelPaintContext { + + @Override + public Raster getRaster(final int x, final int y, final int w, final int h) { + WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h); + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + float hue = (float) ((getWheelHue(x + i, y + j, cx, cy))); + int rgb = Color.HSBtoRGB(hue, 1.0f, 1.0f); + setPixel(raster, i, j, rgb); + } + } + return raster; + } + } + + protected class InnerPaintContext extends ColorWheelPaintContext { + + public InnerPaintContext() { + } + + @Override + public Raster getRaster(final int x, final int y, final int w, final int h) { + WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h); + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + dummy.setLocation((x + i), (y + j)); + try { + transform.inverseTransform(dummy, dummy); + triangleInverse.transform(dummy, dummy); + Point2D sv = getSaturationAndValue(dummy.getX(), dummy.getY()); + setPixel(raster, i, j, getColorRGB(getHue(), sv.getX(), sv.getY())); + } catch (NoninvertibleTransformException ignored) { + } + } + } + return raster; + } + } + + protected static class PickResult { + protected final PickArea area; + protected final double rotation; + protected final double saturation; + protected final double value; + protected final double hue; + + public PickResult(final PickArea area, final double rotation, + final double hue, final double saturation, final double value) { + this.area = area; + this.hue = hue; + this.rotation = rotation; + this.saturation = saturation; + this.value = value; + } + } + + protected enum PickArea { + OUTSIDE, + WHEEL, + TRIANGLE + } +} diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorValueFormatter.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorValueFormatter.java index c84d1a94..50bc8e7f 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorValueFormatter.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorValueFormatter.java @@ -106,15 +106,20 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma } - static ColorValueFormatter init(final DarkColorModel model, final int index, - final boolean hex, final JFormattedTextField text) { + public static ColorValueFormatter init(final DarkColorModel model, final int index, + final boolean hex, final JFormattedTextField text) { ColorValueFormatter formatter = new ColorValueFormatter(model, index, hex); + formatter.setText(text); text.setFormatterFactory(new DefaultFormatterFactory(formatter)); text.setMinimumSize(text.getPreferredSize()); text.addFocusListener(formatter); return formatter; } + public void setText(final JFormattedTextField text) { + this.text = text; + } + protected void error() { text.putClientProperty("JTextComponent.hasError", true); text.repaint(); @@ -213,7 +218,9 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma checkRange(g, 0, 255); int b = Integer.valueOf(hexStr.substring(4, 6), 16); checkRange(b, 0, 255); - int alpha = Integer.valueOf(hexStr.substring(6, 8), 16); + int alpha = hexStr.length() >= 8 + ? Integer.valueOf(hexStr.substring(6, 8), 16) + : 255; checkRange(alpha, 0, 255); return new Color(r, g, b, alpha); } else { diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java deleted file mode 100644 index 41bf6f99..00000000 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 Jannis Weis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.github.weisj.darklaf.ui.colorchooser; - - -import com.github.weisj.darklaf.util.ColorUtil; -import com.github.weisj.darklaf.util.DarkUIUtil; -import com.github.weisj.darklaf.util.GraphicsContext; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -public class ColorWheel extends JComponent { - private static final int BORDER_SIZE = 5; - private final List myListeners = new ArrayList<>(); - protected Color dropFill; - protected Color dropBorder; - protected Color background; - private float myBrightness = 1f; - private float myHue = 1f; - private float mySaturation = 0f; - private Image myImage; - private Rectangle myWheel; - private boolean myShouldInvalidate = true; - private Color myColor; - private int myOpacity; - private boolean pressedInside; - - public ColorWheel() { - setOpaque(true); - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(final ComponentEvent e) { - myShouldInvalidate = true; - } - }); - - addMouseMotionListener(new MouseAdapter() { - @Override - public void mouseDragged(final MouseEvent e) { - if (!pressedInside) return; - final int x = e.getX(); - final int y = e.getY(); - int mx = myWheel.x + myWheel.width / 2; - int my = myWheel.y + myWheel.height / 2; - double s; - double h; - s = Math.sqrt((x - mx) * (x - mx) + (y - my) * (y - my)) / (myWheel.height / 2.0); - h = -Math.atan2(y - my, x - mx) / (2 * Math.PI); - if (h < 0) h += 1.0; - if (s > 1) s = 1.0; - - setHSBValue((float) h, (float) s, myBrightness, myOpacity); - } - }); - - addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - final int x = e.getX(); - final int y = e.getY(); - int mx = myWheel.x + myWheel.width / 2; - int my = myWheel.y + myWheel.height / 2; - double s; - double h; - s = Math.sqrt((x - mx) * (x - mx) + (y - my) * (y - my)) / (myWheel.height / 2.0); - h = -Math.atan2(y - my, x - mx) / (2 * Math.PI); - if (h < 0) h += 1.0; - if (s <= 1) { - pressedInside = true; - setHSBValue((float) h, (float) s, myBrightness, myOpacity); - } else { - pressedInside = false; - } - } - - @Override - public void mouseReleased(final MouseEvent e) { - pressedInside = false; - } - }); - - background = UIManager.getColor("ColorChooser.colorWheelBackground"); - dropFill = UIManager.getColor("ColorChooser.colorWheelDropBackgroundColor"); - dropBorder = UIManager.getColor("ColorChooser.colorWheelDropBorderColor"); - } - - private void setHSBValue(final float h, final float s, final float b, final int opacity) { - Color rgb = new Color(Color.HSBtoRGB(h, s, b)); - setColor(ColorUtil.toAlpha(rgb, opacity), this, h, s, b); - } - - public void setColor(final Color color, final Object source, - final float h, final float s, final float b) { - myColor = color; - myHue = h; - mySaturation = s; - myBrightness = b; - myOpacity = color.getAlpha(); - - fireColorChanged(source); - - repaint(); - } - - private void fireColorChanged(final Object source) { - for (ColorListener listener : myListeners) { - listener.colorChanged(myColor, source); - } - } - - @Override - public void updateUI() { - super.updateUI(); - background = UIManager.getColor("ColorChooser.colorWheelBackground"); - dropFill = UIManager.getColor("ColorChooser.colorWheelDropBackgroundColor"); - dropBorder = UIManager.getColor("ColorChooser.colorWheelDropBorderColor"); - } - - @Override - protected void paintComponent(final Graphics g) { - Graphics2D g2d = (Graphics2D) g; - - final Dimension size = getSize(); - int _size = Math.min(size.width, size.height); - _size = Math.min(_size, 600); - - if (myImage != null && myShouldInvalidate) { - if (myImage.getWidth(null) != _size) { - myImage = null; - } - } - - myShouldInvalidate = false; - - if (myImage == null) { - myImage = createImage(new ColorWheelImageProducer( - _size - BORDER_SIZE * 2, _size - BORDER_SIZE * 2, myBrightness)); - myWheel = new Rectangle(BORDER_SIZE, BORDER_SIZE, _size - BORDER_SIZE * 2, - _size - BORDER_SIZE * 2); - } - - g2d.setColor(background); - g2d.fillRect(0, 0, getWidth(), getHeight()); - - - GraphicsContext config = new GraphicsContext(g); - g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ((float) myOpacity) / 255f)); - g2d.drawImage(myImage, myWheel.x, myWheel.y, null); - config.restore(); - - int mx = myWheel.x + myWheel.width / 2; - int my = myWheel.y + myWheel.height / 2; - int arcw = (int) (myWheel.width * mySaturation / 2); - int arch = (int) (myWheel.height * mySaturation / 2); - double th = myHue * 2 * Math.PI; - final int x = (int) (mx + arcw * Math.cos(th)); - final int y = (int) (my - arch * Math.sin(th)); - - g2d.setColor(dropFill); - g2d.fillRect(x - 2, y - 2, 4, 4); - g2d.setColor(dropBorder); - DarkUIUtil.drawRect(g, x - 2, y - 2, 4, 4, 1); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - return new Dimension(300, 300); - } - - public void addListener(final ColorListener listener) { - myListeners.add(listener); - } - - public void setBrightness(final float brightness) { - if (brightness != myBrightness) { - myImage = null; - setHSBValue(myHue, mySaturation, brightness, myOpacity); - } - } - - public void setOpacity(final int opacity) { - if (opacity != myOpacity) { - setHSBValue(myHue, mySaturation, myBrightness, opacity); - } - } - - public void dropImage() { - myImage = null; - } -} diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java deleted file mode 100644 index 37d3c5e4..00000000 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020 Jannis Weis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.github.weisj.darklaf.ui.colorchooser; - -import java.awt.*; -import java.awt.image.ColorModel; -import java.awt.image.MemoryImageSource; - -/** - * @author pegov - * @author Konstantin Bulenkov - */ -public class ColorWheelImageProducer extends MemoryImageSource { - - private final int[] myPixels; - private final int myWidth; - private final int myHeight; - private final float myBrightness; - - private float[] myHues; - private float[] mySat; - private int[] myAlphas; - - public ColorWheelImageProducer(final int w, final int h, final float brightness) { - super(w, h, null, 0, w); - myPixels = new int[w * h]; - myWidth = w; - myHeight = h; - myBrightness = brightness; - generateLookupTables(); - newPixels(myPixels, ColorModel.getRGBdefault(), 0, w); - setAnimated(true); - generateColorWheel(); - } - - private void generateLookupTables() { - mySat = new float[myWidth * myHeight]; - myHues = new float[myWidth * myHeight]; - myAlphas = new int[myWidth * myHeight]; - float radius = getRadius(); - - // blend is used to create a linear alpha gradient of two extra pixels - float blend = (radius + 2f) / radius - 1f; - - // Center of the color wheel circle - int cx = myWidth / 2; - int cy = myHeight / 2; - - for (int x = 0; x < myWidth; x++) { - int kx = x - cx; // cartesian coordinates of x - int squarekx = kx * kx; // Square of cartesian x - - for (int y = 0; y < myHeight; y++) { - int ky = cy - y; // cartesian coordinates of y - - int index = x + y * myWidth; - mySat[index] = (float) Math.sqrt(squarekx + ky - * ky) - / radius; - if (mySat[index] <= 1f) { - myAlphas[index] = 0xff000000; - } else { - myAlphas[index] = (int) ((blend - Math.min(blend, - mySat[index] - 1f)) * 255 / blend) << 24; - mySat[index] = 1f; - } - if (myAlphas[index] != 0) { - myHues[index] = (float) (Math.atan2(ky, kx) / Math.PI / 2d); - } - } - } - } - - public void generateColorWheel() { - for (int index = 0; index < myPixels.length; index++) { - if (myAlphas[index] != 0) { - myPixels[index] = myAlphas[index] | 0xffffff & Color.HSBtoRGB(myHues[index], - mySat[index], - myBrightness); - } - } - newPixels(); - } - - public int getRadius() { - return Math.min(myWidth, myHeight) / 2 - 2; - } -} diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java index 8adb9ac1..54e08eb9 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java @@ -24,8 +24,9 @@ package com.github.weisj.darklaf.ui.colorchooser; +import com.github.weisj.darklaf.color.DarkColorModel; + import javax.swing.*; -import javax.swing.colorchooser.AbstractColorChooserPanel; import java.awt.*; /** @@ -33,62 +34,42 @@ import java.awt.*; * @author Konstantin Bulenkov */ public class ColorWheelPanel extends JPanel { - private final ColorWheel colorWheel; - private final SlideComponent brightnessSlider; + private final ColorTriangle colorWheel; private SlideComponent opacitySlider = null; private boolean enableOpacity; - public ColorWheelPanel(final ColorListener colorListener, final boolean enableOpacity, - final boolean opacityInPercent) { + public ColorWheelPanel(final boolean enableOpacity, final boolean opacityInPercent) { setLayout(new BorderLayout()); setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); this.enableOpacity = enableOpacity; - colorWheel = new ColorWheel(); + colorWheel = new ColorTriangle(); add(colorWheel, BorderLayout.CENTER); - brightnessSlider = new SlideComponent("Brightness", true, false); - brightnessSlider.setToolTipText("Brightness"); - brightnessSlider.addListener(value -> { - colorWheel.setBrightness(1 - (value / 255f)); - colorWheel.repaint(); - }); - - add(brightnessSlider, BorderLayout.EAST); - colorWheel.addListener(colorListener); - colorWheel.addListener(brightnessSlider); - - if (enableOpacity) { opacitySlider = new SlideComponent("Opacity", false, true); opacitySlider.setToolTipText("Opacity"); opacitySlider.setUnits(opacityInPercent ? SlideComponent.Unit.PERCENT : SlideComponent.Unit.LEVEL); opacitySlider.addListener(integer -> { - colorWheel.setOpacity(integer); - colorWheel.repaint(); + colorWheel.setOpacity(integer / 255.0); + ColorWheelPanel.this.repaint(); }); - add(opacitySlider, BorderLayout.SOUTH); colorWheel.addListener(opacitySlider); - } } - public void setColor(final Color color, final Object source) { - float[] hsb = new float[3]; - Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb); - - brightnessSlider.setValue(255 - (int) (hsb[2] * 255)); - brightnessSlider.repaint(); + public void addListener(final ColorListener listener) { + colorWheel.addListener(listener); + } - colorWheel.dropImage(); - if (opacitySlider != null && source instanceof AbstractColorChooserPanel) { + public void setColor(final Color color, final Object source) { + if (opacitySlider != null) { opacitySlider.setValue(color.getAlpha()); opacitySlider.repaint(); } - - colorWheel.setColor(color, source, hsb[0], hsb[1], hsb[2]); + colorWheel.setColor(source, color); } public boolean isColorTransparencySelectionEnabled() { @@ -102,4 +83,8 @@ public class ColorWheelPanel extends JPanel { opacitySlider.setVisible(b); } } + + public void setModel(final DarkColorModel darkColorModel) { + colorWheel.setColorModel(darkColorModel); + } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserPanel.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserPanel.java index 5e59aded..158721a6 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserPanel.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserPanel.java @@ -28,16 +28,14 @@ import com.github.weisj.darklaf.color.DarkColorModel; import com.github.weisj.darklaf.components.DefaultColorPipette; import com.github.weisj.darklaf.components.uiresource.JButtonUIResource; import com.github.weisj.darklaf.decorators.AncestorAdapter; +import com.github.weisj.darklaf.decorators.UpdateDocumentListener; import com.github.weisj.darklaf.ui.button.DarkButtonUI; import com.github.weisj.darklaf.util.ColorUtil; -import com.github.weisj.darklaf.util.PropertyKey; import javax.swing.*; import javax.swing.colorchooser.AbstractColorChooserPanel; import javax.swing.colorchooser.ColorSelectionModel; import javax.swing.event.AncestorEvent; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; import java.awt.*; import java.awt.event.KeyEvent; @@ -48,62 +46,63 @@ import java.awt.event.KeyEvent; */ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements ColorListener { - public static final String TRANSPARENCY_ENABLED_PROPERTY - = "TransparencyEnabled"; + public static final String TRANSPARENCY_ENABLED_PROPERTY = "transparency"; + + private final Icon pipetteIcon; + private final Icon pipetteHoverIcon; private final ColorPipette pipette; private final ColorWheelPanel colorWheelPanel; - private final JFormattedTextField textHex; - private final ColorValueFormatter hexFormatter; - private final JFormattedTextField[] valueFields; - private final ColorValueFormatter[] formatters; + private final ColorPreviewComponent previewComponent; + + private JFormattedTextField[] valueFields; + private ColorValueFormatter[] formatters; + private JLabel[] descriptors; + private JLabel[] descriptorsAfter; + private JFormattedTextField textHex; + private ColorValueFormatter hexFormatter; private final JComboBox formatBox; - private final ColorPreviewComponent previewComponent; - private final JLabel[] descriptors; - private final JLabel[] descriptorsAfter; - private final boolean doneInit; private Color currentColor; - private boolean isChanging; - private Icon pipetteIcon; - private Icon pipetteHoverIcon; + protected boolean isChanging; public DarkColorChooserPanel(final DarkColorModel... colorModels) { if (colorModels == null || colorModels.length == 0) { throw new IllegalArgumentException("Must pass at least one valid colorModel"); } - + isChanging = true; previewComponent = new ColorPreviewComponent(); - colorWheelPanel = new ColorWheelPanel(this, true, true); + colorWheelPanel = new ColorWheelPanel(true, true); pipette = new DefaultColorPipette(this, colorWheelPanel::setColor); pipetteIcon = UIManager.getIcon("ColorChooser.pipette.icon"); pipetteHoverIcon = UIManager.getIcon("ColorChooser.pipetteRollover.icon"); - formatBox = new JComboBox<>(colorModels); - formatBox.addActionListener(e -> { - updateDescriptors(); - updateValueFields(); - updateFormatters(); - applyColorToFields(getColorFromModel()); - doLayout(); - }); - int record = 0; - DarkColorModel prototype = null; - for (DarkColorModel model : colorModels) { - record = Math.max(model.getValueCount(), record); - String name = model.toString(); - if (prototype == null || prototype.toString().length() < name.length()) { - prototype = model; - } - } + formatBox = createColorFormatChooser(colorModels); - formatBox.setPrototypeDisplayValue(prototype); - descriptors = new JLabel[record]; - descriptorsAfter = new JLabel[record]; + initInputFields(colorModels); - textHex = createColorField(true); + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); + add(buildTopPanel(UIManager.getBoolean("ColorChooser.pipetteEnabled")), BorderLayout.NORTH); + add(colorWheelPanel, BorderLayout.CENTER); + add(Box.createVerticalStrut(10), BorderLayout.SOUTH); + + installListeners(); + // Finalized by #buildChooser + } + + @Override + protected void buildChooser() { + isChanging = false; + onModelChange(); + colorChanged(getColorFromModel(), this); + } + + protected void installListeners() { + formatBox.addActionListener(e -> onModelChange()); + // Make sure the hex field is selected at start. textHex.addAncestorListener(new AncestorAdapter() { @Override public void ancestorAdded(final AncestorEvent event) { @@ -111,6 +110,83 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements textHex.removeAncestorListener(this); } }); + textHex.getDocument().addDocumentListener((UpdateDocumentListener) () -> { + colorChanged(getColorFromHex(), textHex); + }); + for (JFormattedTextField inputField : valueFields) { + inputField.addPropertyChangeListener(e -> colorChanged(getColorFromFields(), inputField)); + } + colorWheelPanel.addListener(this); + } + + protected Color getColorFromHex() { + try { + String hexStr = String.format("%1$-" + 8 + "s", textHex.getText()).replaceAll(" ", "F"); + int alpha = isColorTransparencySelectionEnabled() + ? Integer.valueOf(hexStr.substring(6, 8), 16) : 255; + return new Color( + Integer.valueOf(hexStr.substring(0, 2), 16), + Integer.valueOf(hexStr.substring(2, 4), 16), + Integer.valueOf(hexStr.substring(4, 6), 16), + alpha); + } catch (NumberFormatException | IndexOutOfBoundsException ignore) { + } + return null; + } + + protected Color getColorFromFields() { + DarkColorModel model = getDarkColorModel(); + int[] values = new int[model.getCount()]; + for (int i = 0; i < values.length; i++) { + values[i] = (int) valueFields[i].getValue(); + } + Color c = model.getColorFromValues(values); + if (isColorTransparencySelectionEnabled()) { + c = ColorUtil.toAlpha(c, getColorFromModel().getAlpha()); + } + return c; + } + + @Override + public void colorChanged(final Color color, final Object source) { + if (isChanging || color == null) return; + isChanging = true; + currentColor = color; + ColorSelectionModel model = getColorSelectionModel(); + if (model != null) model.setSelectedColor(currentColor); + applyColorToFields(color); + if (source != textHex) textHex.setValue(color); + previewComponent.setColor(color); + colorWheelPanel.setColor(color, this); + isChanging = false; + } + + protected void onModelChange() { + if (isChanging) return; + isChanging = true; + colorWheelPanel.setModel(getDarkColorModel()); + updateDescriptors(); + toggleValueFields(); + updateFormatters(); + applyColorToFields(getColorFromModel()); + doLayout(); + isChanging = false; + } + + protected void applyColorToFields(final Color color) { + DarkColorModel model = getDarkColorModel(); + int[] values = model.getValuesFromColor(color); + for (int i = 0; i < values.length; i++) { + valueFields[i].setValue(values[i]); + } + } + + public void initInputFields(final DarkColorModel[] colorModels) { + int record = getMaxFieldCount(colorModels); + descriptors = new JLabel[record]; + descriptorsAfter = new JLabel[record]; + + textHex = createColorField(true); hexFormatter = ColorValueFormatter.init(getDarkColorModel(), 0, true, textHex); hexFormatter.setTransparencyEnabled(isColorTransparencySelectionEnabled()); @@ -124,15 +200,34 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements valueFields[i] = createColorField(false); formatters[i] = ColorValueFormatter.init(getDarkColorModel(), i, false, valueFields[i]); } + } - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - add(buildTopPanel(UIManager.getBoolean("ColorChooser.pipetteEnabled")), BorderLayout.NORTH); - add(colorWheelPanel, BorderLayout.CENTER); - add(Box.createVerticalStrut(10), BorderLayout.SOUTH); - updateValueFields(); - updateDescriptors(); - doneInit = true; + private JFormattedTextField createColorField(final boolean hex) { + JFormattedTextField field = new JFormattedTextField(0); + field.setColumns(hex ? 8 : 3); + field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); + return field; + } + + protected int getMaxFieldCount(final DarkColorModel[] colorModels) { + int record = 0; + for (DarkColorModel model : colorModels) { + record = Math.max(model.getCount(), record); + } + return record; + } + + protected JComboBox createColorFormatChooser(final DarkColorModel[] colorModels) { + JComboBox comboBox = new JComboBox<>(colorModels); + DarkColorModel prototype = null; + for (DarkColorModel model : colorModels) { + String name = model.toString(); + if (prototype == null || prototype.toString().length() < name.length()) { + prototype = model; + } + } + comboBox.setPrototypeDisplayValue(prototype); + return comboBox; } private void updateDescriptors() { @@ -152,9 +247,9 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements } } - private void updateValueFields() { + private void toggleValueFields() { DarkColorModel model = getDarkColorModel(); - int count = model.getValueCount(); + int count = model.getCount(); for (int i = 0; i < valueFields.length; i++) { valueFields[i].setEnabled(i < count); valueFields[i].setVisible(i < count); @@ -167,22 +262,13 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements } } - private void applyColorToFields(final Color color) { - DarkColorModel model = getDarkColorModel(); - isChanging = true; - int[] values = model.getValuesFromColor(color); - for (int i = 0; i < values.length; i++) { - valueFields[i].setValue(values[i]); - } - isChanging = false; - } - @Override protected Color getColorFromModel() { Color c = super.getColorFromModel(); return c == null ? currentColor : c; } + protected DarkColorModel getDarkColorModel() { return (DarkColorModel) formatBox.getSelectedItem(); } @@ -193,22 +279,7 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements final JPanel previewPanel = new JPanel(new BorderLayout()); if (enablePipette && pipette != null) { - JButton pipetteButton = new JButtonUIResource(); - pipetteButton.putClientProperty(DarkButtonUI.KEY_VARIANT, DarkButtonUI.VARIANT_ONLY_LABEL); - pipetteButton.putClientProperty(DarkButtonUI.KEY_THIN, Boolean.TRUE); - pipetteButton.setRolloverEnabled(true); - pipetteButton.setIcon(getPipetteIcon()); - pipetteButton.setRolloverIcon(getPipetteRolloverIcon()); - pipetteButton.setDisabledIcon(getPipetteRolloverIcon()); - pipetteButton.setPressedIcon(getPipetteRolloverIcon()); - pipetteButton.setFocusable(false); - pipetteButton.addActionListener(e -> { - pipetteButton.setEnabled(false); - pipette.setInitialColor(getColorFromModel()); - pipette.show(); - }); - ((DefaultColorPipette) pipette).setCloseAction(() -> pipetteButton.setEnabled(true)); - previewPanel.add(pipetteButton, BorderLayout.WEST); + previewPanel.add(createPipetteButton(), BorderLayout.WEST); } previewPanel.add(previewComponent, BorderLayout.CENTER); result.add(previewPanel, BorderLayout.NORTH); @@ -242,51 +313,39 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements return result; } + private JButton createPipetteButton() { + JButton pipetteButton = new JButtonUIResource(); + pipetteButton.putClientProperty(DarkButtonUI.KEY_VARIANT, DarkButtonUI.VARIANT_ONLY_LABEL); + pipetteButton.putClientProperty(DarkButtonUI.KEY_THIN, Boolean.TRUE); + pipetteButton.setRolloverEnabled(true); + pipetteButton.setIcon(getPipetteIcon()); + pipetteButton.setRolloverIcon(getPipetteRolloverIcon()); + pipetteButton.setDisabledIcon(getPipetteRolloverIcon()); + pipetteButton.setPressedIcon(getPipetteRolloverIcon()); + pipetteButton.setFocusable(false); + pipetteButton.addActionListener(e -> { + pipetteButton.setEnabled(false); + pipette.setInitialColor(getColorFromModel()); + pipette.show(); + }); + ((DefaultColorPipette) pipette).setCloseAction(() -> pipetteButton.setEnabled(true)); + return pipetteButton; + } - private JFormattedTextField createColorField(final boolean hex) { - JFormattedTextField field = new JFormattedTextField(0); - field.setColumns(hex ? 8 : 4); - if (!hex) { - field.addPropertyChangeListener(e -> { - if (PropertyKey.VALUE.equals(e.getPropertyName())) { - updatePreviewFromTextFields(); - } - }); - } else { - field.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(final DocumentEvent e) { - update(); - } - - @Override - public void removeUpdate(final DocumentEvent e) { - update(); - } - - @Override - public void changedUpdate(final DocumentEvent e) { - } - - protected void update() { - try { - if (isChanging) return; - String hexStr = String.format("%1$-" + 8 + "s", field.getText()).replaceAll(" ", "F"); - int alpha = isColorTransparencySelectionEnabled() - ? Integer.valueOf(hexStr.substring(6, 8), 16) : 255; - Color c = new Color( - Integer.valueOf(hexStr.substring(0, 2), 16), - Integer.valueOf(hexStr.substring(2, 4), 16), - Integer.valueOf(hexStr.substring(4, 6), 16), - alpha); - colorWheelPanel.setColor(c, textHex); - } catch (NumberFormatException | IndexOutOfBoundsException ignore) { - } - } - }); + public boolean isColorTransparencySelectionEnabled() { + return colorWheelPanel.isColorTransparencySelectionEnabled(); + } + + public void setColorTransparencySelectionEnabled(final boolean b) { + boolean oldValue = isColorTransparencySelectionEnabled(); + if (b != oldValue) { + hexFormatter.setTransparencyEnabled(b); + colorWheelPanel.setColorTransparencySelectionEnabled(b); + if (b && getColorFromModel().getAlpha() < 255) { + colorChanged(ColorUtil.removeAlpha(getColorFromModel()), this); + } + firePropertyChange(TRANSPARENCY_ENABLED_PROPERTY, oldValue, b); } - field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); - return field; } protected Icon getPipetteIcon() { @@ -304,15 +363,6 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements @Override public void updateChooser() { - if (isChanging) return; - Color color = getColorFromModel(); - if (color != null) { - colorWheelPanel.setColor(color, this); - } - } - - @Override - protected void buildChooser() { } @Override @@ -335,83 +385,8 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements return null; } - public boolean isColorTransparencySelectionEnabled() { - return colorWheelPanel.isColorTransparencySelectionEnabled(); - } - @Override public Icon getLargeDisplayIcon() { return null; } - - private void updatePreviewFromTextFields() { - if (!doneInit || isChanging) return; - isChanging = true; - int[] values = new int[valueFields.length]; - for (int i = 0; i < valueFields.length; i++) { - values[i] = (((Integer) valueFields[i].getValue())); - } - Color color = getDarkColorModel().getColorFromValues(values); - - if (isColorTransparencySelectionEnabled()) { - color = ColorUtil.toAlpha(color, getColorFromModel().getAlpha()); - } - colorWheelPanel.setColor(color, valueFields[0]); - isChanging = false; - } - - public void setColorTransparencySelectionEnabled(final boolean b) { - boolean oldValue = isColorTransparencySelectionEnabled(); - if (b != oldValue) { - Color color = getColorFromModel(); - color = new Color(color.getRed(), color.getBlue(), color.getGreen()); - ColorSelectionModel model = getColorSelectionModel(); - if (model != null) { - model.setSelectedColor(color); - } - currentColor = color; - hexFormatter.setTransparencyEnabled(b); - colorWheelPanel.setColorTransparencySelectionEnabled(b); - applyColorToHEX(getColorFromModel()); - firePropertyChange(TRANSPARENCY_ENABLED_PROPERTY, - oldValue, b); - } - } - - @Override - public void colorChanged(final Color color, final Object source) { - isChanging = true; - if (color != null && !color.equals(currentColor)) { - Color newColor = !isColorTransparencySelectionEnabled() - ? new Color(color.getRed(), color.getGreen(), color.getBlue()) : color; - ColorSelectionModel model = getColorSelectionModel(); - if (model != null) { - model.setSelectedColor(newColor); - } - currentColor = newColor; - previewComponent.setColor(newColor); - if (!(source instanceof JFormattedTextField)) { - applyColorToFields(newColor); - } - if (source != textHex) { - applyColorToHEX(newColor); - } - } - isChanging = false; - } - - - private void applyColorToHEX(final Color c) { - boolean changingOld = isChanging; - isChanging = true; - boolean transparencyEnabled = isColorTransparencySelectionEnabled(); - if (transparencyEnabled) { - textHex.setValue(c); - } else { - textHex.setValue(ColorUtil.removeAlpha(c)); - } - isChanging = changingOld; - } - - } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java index dec910ea..da432301 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java @@ -23,9 +23,10 @@ */ package com.github.weisj.darklaf.ui.colorchooser; -import com.github.weisj.darklaf.color.DarkColorModel; import com.github.weisj.darklaf.color.DarkColorModelCMYK; +import com.github.weisj.darklaf.color.DarkColorModelHSB; import com.github.weisj.darklaf.color.DarkColorModelHSL; +import com.github.weisj.darklaf.color.DarkColorModelRGB; import com.github.weisj.darklaf.decorators.AncestorAdapter; import com.github.weisj.darklaf.util.PropertyKey; @@ -80,10 +81,10 @@ public class DarkColorChooserUI extends BasicColorChooserUI { @Override protected AbstractColorChooserPanel[] createDefaultChoosers() { return new AbstractColorChooserPanel[]{ - new DarkColorChooserPanel(new DarkColorModel(), - new DarkColorModelHSL(), -// new DarkColorModelHSB(), - new DarkColorModelCMYK()), + new DarkColorChooserPanel(DarkColorModelRGB.getInstance(), + DarkColorModelHSB.getInstance(), + DarkColorModelHSL.getInstance(), + DarkColorModelCMYK.getInstance()), new DarkSwatchesChooserPanel(), }; } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java index ffd4fe10..9379c051 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java @@ -108,7 +108,6 @@ class SlideComponent extends JComponent implements ColorListener { @Override public void componentResized(final ComponentEvent e) { setValue(getValue()); - fireValueChanged(); repaint(); } }); @@ -160,8 +159,9 @@ class SlideComponent extends JComponent implements ColorListener { } public void setValue(final int value) { - if (value < 0 || value > 255) { - throw new IllegalArgumentException("Value " + value + " not in range [0,255]"); + if (value < Unit.LEVEL.getMin() || value > Unit.LEVEL.getMax()) { + throw new IllegalArgumentException( + "Value " + value + " not in range [" + Unit.LEVEL.getMin() + "," + Unit.LEVEL.getMax() + "]"); } pointerValue = valueToPointerValue(value); this.value = value; @@ -292,23 +292,45 @@ class SlideComponent extends JComponent implements ColorListener { } enum Unit { - PERCENT, - LEVEL; + LEVEL(0, 255f) { + @Override + public String formatValue(final int value) { + return String.format("%d", (int) (LEVEL.max - value)); + } + }, + PERCENT(0, 100f) { + @Override + public String formatValue(final int value) { + return String.format("%d%s", (int) (value * ((max - min) / (LEVEL.max - LEVEL.min))), "%"); + + } + }; - private static final float PERCENT_MAX_VALUE = 100f; - private static final float LEVEL_MAX_VALUE = 255f; + + protected final float max; + protected final float min; + + Unit(final float min, final float max) { + this.max = max; + this.min = min; + } private static String formatValue(final int value, final Unit unit) { if (unit == PERCENT) { - return String.format("%d%s", (int) ((getMaxValue(unit) / LEVEL_MAX_VALUE * value)), "%"); + return String.format("%d%s", (int) ((unit.getMax() / LEVEL.getMax() * value)), "%"); } else { - return String.format("%d", (int) (LEVEL_MAX_VALUE - ((getMaxValue(unit) / LEVEL_MAX_VALUE * value)))); + return String.format("%d", (int) (LEVEL.getMax() - ((unit.getMax() / LEVEL.getMax() * value)))); } } + public abstract String formatValue(final int value); + + public float getMax() { + return max; + } - private static float getMaxValue(final Unit unit) { - return LEVEL.equals(unit) ? LEVEL_MAX_VALUE : PERCENT_MAX_VALUE; + public float getMin() { + return min; } } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java index a18341ae..cf43fa2c 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java @@ -23,6 +23,7 @@ */ package com.github.weisj.darklaf.ui.popupmenu; +import com.github.weisj.darklaf.ui.DarkPopupFactory; import sun.awt.SunToolkit; import javax.swing.*; @@ -86,6 +87,12 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI { return mouseGrabber; } + @Override + public void installDefaults() { + super.installDefaults(); + popupMenu.putClientProperty(DarkPopupFactory.KEY_START_HIDDEN, true); + } + @Override protected void installListeners() { super.installListeners(); diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkRootPaneUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkRootPaneUI.java index 6eba5b68..1b9b1e1d 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkRootPaneUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkRootPaneUI.java @@ -49,7 +49,6 @@ public class DarkRootPaneUI extends BasicRootPaneUI implements HierarchyListener protected static final String KEY_PREFIX = "JRootPane."; public static final String KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT = "mediumWeightPopupRoot"; public static final String KEY_IS_POPUP = KEY_PREFIX + "isPopup"; - public static final String KEY_IS_TOOLTIP = KEY_PREFIX + "isToolTip"; private Window window; private CustomTitlePane titlePane; private LayoutManager layoutManager; diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java index a1332d76..8b2cbfdf 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java @@ -167,7 +167,7 @@ public class DarkSliderUI extends BasicSliderUI implements PropertyChangeListene } @Override - protected Dimension getThumbSize() { + public Dimension getThumbSize() { if (isPlainThumb()) { return new Dimension(plainThumbRadius + 6, plainThumbRadius + 6); } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java index 490d348a..cefb07af 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java @@ -47,6 +47,7 @@ public class DarkTooltipBorder implements Border { private final DropShadowBorder shadowBorder = new DropShadowBorder(Color.BLACK, shadowSize, opacity, cornerSize, false, true, true, true); private final BubbleBorder bubbleBorder; + private boolean skipShadow; public DarkTooltipBorder() { bubbleBorder = new BubbleBorder(UIManager.getColor("ToolTip.borderColor")); @@ -103,7 +104,7 @@ public class DarkTooltipBorder implements Border { Area bubbleArea = bubbleBorder.getInnerArea(x + ins.left, y + ins.top, width - ins.left - ins.right, height - ins.top - ins.bottom); - if (UIManager.getBoolean("ToolTip.paintShadow")) { + if (!skipShadow && UIManager.getBoolean("ToolTip.paintShadow")) { paintShadow(c, g, x, y, width, height, bubbleArea); } bubbleBorder.paintBorder(g, bubbleArea); @@ -185,4 +186,8 @@ public class DarkTooltipBorder implements Border { if (isPlain(c)) return 0; return shadowBorder.getShadowSize(); } + + public void setSkipShadow(final boolean skip) { + this.skipShadow = skip; + } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java index 2fe140b6..46996c93 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java @@ -24,7 +24,7 @@ package com.github.weisj.darklaf.ui.tooltip; import com.github.weisj.darklaf.components.tooltip.ToolTipStyle; -import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI; +import com.github.weisj.darklaf.ui.DarkPopupFactory; import com.github.weisj.darklaf.util.*; import javax.swing.*; @@ -115,6 +115,9 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe @Override protected void installDefaults(final JComponent c) { super.installDefaults(c); + toolTip.putClientProperty(DarkPopupFactory.KEY_NO_DECORATION, true); + toolTip.putClientProperty(DarkPopupFactory.KEY_START_HIDDEN, true); + toolTip.putClientProperty(DarkPopupFactory.KEY_FORCE_HEAVYWEIGHT, true); fadeAnimator = new FadeInAnimator(); c.setOpaque(false); DarkTooltipBorder border = new DarkTooltipBorder(); @@ -202,14 +205,14 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe ((JComponent) toolTip.getParent()).setOpaque(false); } if (lastRootPane != null) { - lastRootPane.putClientProperty(DarkRootPaneUI.KEY_IS_TOOLTIP, false); + lastRootPane.putClientProperty(DarkPopupFactory.KEY_NO_DECORATION, false); } if (w != null && !isDecorated(w) && (w.getClass().getEnclosingClass().equals(Popup.class))) { w.setBackground(DarkUIUtil.TRANSPARENT_COLOR); if (w instanceof RootPaneContainer) { lastRootPane = ((RootPaneContainer) w).getRootPane(); if (lastRootPane != null) { - lastRootPane.putClientProperty(DarkRootPaneUI.KEY_IS_TOOLTIP, true); + lastRootPane.putClientProperty(DarkPopupFactory.KEY_NO_DECORATION, true); } } } @@ -351,7 +354,8 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe public FadeInAnimator() { - super("Tooltip fadein", FADEIN_FRAMES_COUNT, FADEIN_FRAMES_COUNT * 20, false); + super("Tooltip fadein", FADEIN_FRAMES_COUNT, + FADEIN_FRAMES_COUNT * 15, false); } @Override @@ -359,13 +363,23 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe alpha = ((float) frame * MAX_ALPHA) / totalFrames; Window window = SwingUtilities.getWindowAncestor(toolTip); if (window != null) window.setOpacity(alpha); + Border border = toolTip.getBorder(); + if (border instanceof DarkTooltipBorder) { + ((DarkTooltipBorder) border).setSkipShadow(false); + } } @Override protected void paintCycleEnd() { alpha = MAX_ALPHA; Window window = SwingUtilities.getWindowAncestor(toolTip); - if (window != null) window.setOpacity(alpha); + if (window != null) { + window.setOpacity(alpha); + Border border = toolTip.getBorder(); + if (window.getFocusableWindowState() && border instanceof DarkTooltipBorder) { + ((DarkTooltipBorder) border).setSkipShadow(true); + } + } } } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java index 139869ef..65a914c5 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java @@ -43,7 +43,7 @@ public class ToolTipUtil { JComponent target = toolTip.getComponent(); if (target == null) return; - ToolTipContext context = getToolTipContext(target); + ToolTipContext context = getToolTipContext(toolTip); if (context == null) return; context.setTarget(target); @@ -92,6 +92,7 @@ public class ToolTipUtil { if (pos == null) { context.setAlignment(Alignment.CENTER); context.setCenterAlignment(Alignment.CENTER); + pos = context.getFallBackPosition(); } context.updateToolTip(); context.setAlignment(original); @@ -137,12 +138,16 @@ public class ToolTipUtil { && SwingUtilities.isRectangleContainingRectangle(screenBoundary, toolTipBounds); } - protected static ToolTipContext getToolTipContext(final JComponent comp) { - Object context = comp.getClientProperty(DarkTooltipUI.KEY_CONTEXT); + protected static ToolTipContext getToolTipContext(final JToolTip tooltip) { + Object context = tooltip.getClientProperty(DarkTooltipUI.KEY_CONTEXT); if (context instanceof ToolTipContext) { return (ToolTipContext) context; } - Object style = comp.getClientProperty(DarkTooltipUI.KEY_STYLE); + context = tooltip.getComponent().getClientProperty(DarkTooltipUI.KEY_CONTEXT); + if (context instanceof ToolTipContext) { + return (ToolTipContext) context; + } + Object style = tooltip.getComponent().getClientProperty(DarkTooltipUI.KEY_STYLE); if (ToolTipStyle.BALLOON.equals(DarkTooltipUI.getStyle(style))) { return DEFAULT_CONTEXT; } @@ -183,7 +188,6 @@ public class ToolTipUtil { return config; } } - return null; } } diff --git a/core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java b/core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java index da9c2ea5..fa099a01 100644 --- a/core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java +++ b/core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java @@ -63,12 +63,40 @@ public class GraphicsContext { } public void restore() { - graphics2D.setRenderingHints(this.hintsMap); + restoreRenderingHints(); + restoreComposite(); + restoreStroke(); + restoreColor(); + restorePaint(); + restoreFont(); + restoreClip(); + } + + public void restoreComposite() { graphics2D.setComposite(composite); + } + + public void restoreFont() { + graphics2D.setFont(font); + } + + public void restoreRenderingHints() { + graphics2D.setRenderingHints(this.hintsMap); + } + + public void restoreStroke() { graphics2D.setStroke(stroke); + } + + public void restoreColor() { graphics2D.setColor(color); + } + + public void restorePaint() { graphics2D.setPaint(paint); - graphics2D.setFont(font); + } + + public void restoreClip() { graphics2D.setClip(clip); } } diff --git a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties index 4f7ffc17..9a768b89 100644 --- a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties +++ b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties @@ -39,6 +39,9 @@ ColorChooser.sliderShadow = %shadow ColorChooser.errorDelay = 600 ColorChooser.swatchesDefaultRecentColor = %background ColorChooser.colorWheelBackground = %background +ColorChooser.outerIndicatorRadius = 3 +ColorChooser.innerIndicatorRadius = 3 + #Icons ColorChooser.pipette.icon = misc/pipette.svg[themed] ColorChooser.pipetteRollover.icon = misc/pipetteRollover.svg[themed] diff --git a/core/src/test/java/ui/QuickColorChooser.java b/core/src/test/java/ui/QuickColorChooser.java index 53450099..76aac280 100644 --- a/core/src/test/java/ui/QuickColorChooser.java +++ b/core/src/test/java/ui/QuickColorChooser.java @@ -23,10 +23,13 @@ */ package ui; +import com.github.weisj.darklaf.components.color.PopupColorChooser; import com.github.weisj.darklaf.decorators.MouseClickListener; +import com.github.weisj.darklaf.icons.EmptyIcon; import javax.swing.*; import java.awt.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -42,21 +45,28 @@ public class QuickColorChooser extends JPanel { public QuickColorChooser(final String title, final Color color, final BiConsumer onStatusChange, final boolean showCheckBox) { super(new FlowLayout(FlowLayout.LEFT, 0, 0)); - icon = new SolidColorIcon(color); - JLabel label = new JLabel(title, icon, JLabel.LEFT); checkBox = new JCheckBox(); if (showCheckBox) { checkBox.addActionListener(e -> onStatusChange.accept(isSelected(), getColor())); add(checkBox); } - label.addMouseListener((MouseClickListener) e -> { - Color c = JColorChooser.showDialog(QuickColorChooser.this, title, icon.getColor()); - if (c != null) { - onStatusChange.accept(isSelected(), c); - icon.setColor(c); - } + + icon = new SolidColorIcon(color); + JLabel colorLabel = new JLabel(icon, JLabel.LEFT); + AtomicBoolean isShowing = new AtomicBoolean(false); + colorLabel.addMouseListener((MouseClickListener) e -> { + if (isShowing.get()) return; + isShowing.set(true); + PopupColorChooser.showColorChooser(colorLabel, icon.getColor(), c -> { + if (c != null) { + onStatusChange.accept(isSelected(), c); + icon.setColor(c); + colorLabel.repaint(); + } + }, () -> isShowing.set(false)); }); - add(label); + add(colorLabel); + add(new JLabel(title, EmptyIcon.create(2, 2), JLabel.LEFT)); } public QuickColorChooser(final String title, final Color color, final BiConsumer onStatusChange) { diff --git a/core/src/test/java/ui/text/FormattedTextFieldDemo.java b/core/src/test/java/ui/text/FormattedTextFieldDemo.java new file mode 100644 index 00000000..555a48c1 --- /dev/null +++ b/core/src/test/java/ui/text/FormattedTextFieldDemo.java @@ -0,0 +1,45 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package ui.text; + +import ui.ComponentDemo; + +import javax.swing.*; + +public class FormattedTextFieldDemo extends TextFieldDemo { + + public static void main(final String[] args) { + ComponentDemo.showDemo(new FormattedTextFieldDemo()); + } + + @Override + protected JTextField createTextField() { + return new JFormattedTextField("Demo FormattedTextField"); + } + + @Override + public String getTitle() { + return "FormattedTextField Demo"; + } +} diff --git a/core/src/test/java/ui/text/TextFieldDemo.java b/core/src/test/java/ui/text/TextFieldDemo.java index 4ce163cd..477197d8 100644 --- a/core/src/test/java/ui/text/TextFieldDemo.java +++ b/core/src/test/java/ui/text/TextFieldDemo.java @@ -38,7 +38,7 @@ public class TextFieldDemo implements ComponentDemo { @Override public JComponent createComponent() { - JTextField textField = new JTextField("Demo TextField"); + JTextField textField = createTextField(); DemoPanel panel = new DemoPanel(textField); JPanel controlPanel = panel.addControls(); @@ -68,6 +68,10 @@ public class TextFieldDemo implements ComponentDemo { return panel; } + protected JTextField createTextField() { + return new JTextField("Demo TextField"); + } + @Override public String getTitle() { return "TextField Demo";