Browse Source

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.
pull/127/head
weisj 5 years ago
parent
commit
0da11ed70a
  1. 29
      core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java
  2. 17
      core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelCMYK.java
  3. 115
      core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java
  4. 39
      core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSL.java
  5. 69
      core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelRGB.java
  6. 134
      core/src/main/java/com/github/weisj/darklaf/components/color/PopupColorChooser.java
  7. 297
      core/src/main/java/com/github/weisj/darklaf/components/color/SmallColorChooser.java
  8. 12
      core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java
  9. 43
      core/src/main/java/com/github/weisj/darklaf/decorators/UpdateDocumentListener.java
  10. 42
      core/src/main/java/com/github/weisj/darklaf/ui/DarkPopupFactory.java
  11. 10
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java
  12. 678
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorTriangle.java
  13. 13
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorValueFormatter.java
  14. 223
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java
  15. 109
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java
  16. 49
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java
  17. 379
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserPanel.java
  18. 11
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java
  19. 44
      core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java
  20. 7
      core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java
  21. 1
      core/src/main/java/com/github/weisj/darklaf/ui/rootpane/DarkRootPaneUI.java
  22. 2
      core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java
  23. 7
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipBorder.java
  24. 24
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java
  25. 14
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java
  26. 32
      core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java
  27. 3
      core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties
  28. 28
      core/src/test/java/ui/QuickColorChooser.java
  29. 45
      core/src/test/java/ui/text/FormattedTextFieldDemo.java
  30. 6
      core/src/test/java/ui/text/TextFieldDemo.java

29
core/src/main/java/com/github/weisj/darklaf/color/DarkColorModel.java

@ -29,17 +29,12 @@ import java.awt.*;
/** /**
* @author Jannis Weis * @author Jannis Weis
*/ */
public class DarkColorModel { public abstract class DarkColorModel {
private final String prefix; private final String prefix;
private final String[] labels; private final String[] labels;
public DarkColorModel() {
this("rgb", "Red", "Green", "Blue");
}
public DarkColorModel(final String name, final String... labels) { public DarkColorModel(final String name, final String... labels) {
this.prefix = "ColorChooser." + name; this.prefix = "ColorChooser." + name;
this.labels = labels; this.labels = labels;
@ -49,16 +44,12 @@ public class DarkColorModel {
return this.labels.length; return this.labels.length;
} }
public int getMinimum(final int index) { public abstract int getMinimum(final int index);
return 0;
}
public int getMaximum(final int index) { public abstract int getMaximum(final int index);
return 255;
}
public int getDefault(final int index) { public int getDefault(final int index) {
return 0; return getMinimum(index);
} }
public final String getText(final Component component, final String suffix) { public final String getText(final Component component, final String suffix) {
@ -66,16 +57,12 @@ public class DarkColorModel {
} }
@Override @Override
public String toString() { public abstract String toString();
return "RGB";
}
public int getValueCount() { public abstract char[] getLabelDescriptorsBefore();
return 3;
}
public char[] getLabelDescriptorsBefore() { public String[] getFullLabelDescriptorsBefore() {
return new char[]{'R', 'G', 'B'}; return labels;
} }
public char[] getLabelDescriptorsAfter() { public char[] getLabelDescriptorsAfter() {

17
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[] cmyk = new int[4];
private static final int[] rgb = new int[3]; 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() { public DarkColorModelCMYK() {
super("cmyk", "Cyan", "Magenta", "Yellow", "Black"); super("cmyk", "Cyan", "Magenta", "Yellow", "Black");
} }
@Override
public int getMinimum(final int index) {
return 0;
}
@Override @Override
public int getMaximum(final int index) { public int getMaximum(final int index) {
return 100; return 100;
@ -47,11 +59,6 @@ public class DarkColorModelCMYK extends DarkColorModel {
return "CMYK"; return "CMYK";
} }
@Override
public int getValueCount() {
return 4;
}
@Override @Override
public char[] getLabelDescriptorsBefore() { public char[] getLabelDescriptorsBefore() {
return new char[]{'C', 'M', 'Y', 'K'}; return new char[]{'C', 'M', 'Y', 'K'};

115
core/src/main/java/com/github/weisj/darklaf/color/DarkColorModelHSB.java

@ -30,13 +30,25 @@ import java.awt.*;
*/ */
public class DarkColorModelHSB extends DarkColorModel { 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[] 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() { public DarkColorModelHSB() {
super("hsv", "Hue", "Saturation", "Brightness"); super("hsv", "Hue", "Saturation", "Brightness");
} }
@Override
public int getMinimum(final int index) {
return 0;
}
@Override @Override
public int getMaximum(final int index) { public int getMaximum(final int index) {
return (index == 0) ? 359 : 100; return (index == 0) ? 359 : 100;
@ -62,96 +74,25 @@ public class DarkColorModelHSB extends DarkColorModel {
return RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue()); return RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue());
} }
private static int[] RGBtoHSB(final int r, final int g, final int b) {
public static int[] RGBtoHSB(final int r, final int g, final int b) { double[] values = RGBtoHSBValues(r, g, b);
double hue, saturation, brightness; hsb[0] = (int) Math.round(values[0] * 360);
int cmax = Math.max(r, g); hsb[1] = (int) Math.round(values[1] * 100);
if (b > cmax) cmax = b; hsb[1] = (int) Math.round(values[2] * 100);
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);
return hsb; return hsb;
} }
@Override public static double[] RGBtoHSBValues(final int r, final int g, final int b) {
public Color getColorFromValues(final int[] values) { float[] values = Color.RGBtoHSB(r, g, b, hsvVals);
int[] rgb = HSBtoRGB(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0); return new double[]{values[0], values[1], values[2]};
return new Color(rgb[0], rgb[1], rgb[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) { @Override
double r = 0, g = 0, b = 0; public Color getColorFromValues(final int[] values) {
if (saturation == 0) { return getColorFromHSBValues(values[0] / 360.0, values[1] / 100.0, values[2] / 100.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;
} }
} }

39
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[] hsl = new int[3];
private static final int[] rgb = 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() { public DarkColorModelHSL() {
super("hsl", "Hue", "Saturation", "Lightness"); super("hsl", "Hue", "Saturation", "Lightness");
} }
@Override
public int getMinimum(final int index) {
return 0;
}
@Override @Override
public int getMaximum(final int index) { public int getMaximum(final int index) {
return (index == 0) ? 359 : 100; return (index == 0) ? 359 : 100;
@ -62,8 +74,12 @@ public class DarkColorModelHSL extends DarkColorModel {
return RGBtoHSL(color.getRed(), color.getGreen(), color.getBlue()); 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 max = max(r, g, b) / 255.0;
double min = min(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 ? 2.0f - summa
: summa; : summa;
} }
hsl[0] = (int) Math.round(360 * getHue(r / 255.0, g / 255.0, b / 255.0, max, min)); return new double[]{
hsl[1] = (int) Math.round(100 * saturation); getHue(r / 255.0, g / 255.0, b / 255.0, max, min),
hsl[2] = (int) Math.round(100 * (summa / 2.0)); saturation,
return hsl; 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) { protected static double max(final double red, final double green, final double blue) {
double max = Math.max(red, green); double max = Math.max(red, green);
@ -117,10 +141,10 @@ public class DarkColorModelHSL extends DarkColorModel {
return new Color(rgb[0], rgb[1], rgb[2]); return new Color(rgb[0], rgb[1], rgb[2]);
} }
private static int[] HSLtoRGB(final double h, final double saturation, final double lightness) { private static int[] HSLtoRGB(final double h, final double saturation, final double lightness) {
double hue = h; double hue = h;
while (hue < 0) hue += 1;
hue = hue - Math.floor(hue);
if (saturation > 0.0f) { if (saturation > 0.0f) {
hue = (hue < 1.0f) ? hue * 6.0f : 0.0f; hue = (hue < 1.0f) ? hue * 6.0f : 0.0f;
double q = lightness + saturation * ((lightness > 0.5f) ? 1.0f - lightness : lightness); double q = lightness + saturation * ((lightness > 0.5f) ? 1.0f - lightness : lightness);
@ -136,7 +160,6 @@ public class DarkColorModelHSL extends DarkColorModel {
return rgb; return rgb;
} }
private static double normalize(final double q, final double p, final double color) { private static double normalize(final double q, final double p, final double color) {
if (color < 1.0f) { if (color < 1.0f) {
return p + (q - p) * color; return p + (q - p) * color;

69
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]);
}
}

134
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<Color> 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<Color> 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<Color> 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<Runnable> 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();
}
}

297
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<Color> callback;
protected boolean valueChanging;
protected JTabbedPane colorModelTabbedPane;
protected JFormattedTextField hexField;
protected Map<DarkColorModel, Runnable> updateMap = new HashMap<>();
protected ColorValueFormatter hexFormatter;
public SmallColorChooser(final Color initial, final Consumer<Color> 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<Color> 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);
}
}
}

12
core/src/main/java/com/github/weisj/darklaf/components/tooltip/ToolTipContext.java

@ -80,6 +80,7 @@ public class ToolTipContext {
private ToolTipStyle style; private ToolTipStyle style;
private boolean ignoreBorder; private boolean ignoreBorder;
private boolean bestFit; private boolean bestFit;
private Function<ToolTipContext, Point> fallBackPositionProvider;
/** /**
* Create a new tooltip context to ease the creation of custom tooltips. * Create a new tooltip context to ease the creation of custom tooltips.
@ -115,6 +116,7 @@ public class ToolTipContext {
setToolTipStyle(ToolTipStyle.BALLOON); setToolTipStyle(ToolTipStyle.BALLOON);
setUpdatePosition(false); setUpdatePosition(false);
setHideOnExit(false); setHideOnExit(false);
setFallBackPositionProvider(null);
setAlignInside(alignInside); setAlignInside(alignInside);
setAlignment(alignment); setAlignment(alignment);
setCenterAlignment(centerAlignment); setCenterAlignment(centerAlignment);
@ -589,4 +591,14 @@ public class ToolTipContext {
this.toolTip.setComponent(this.target); this.toolTip.setComponent(this.target);
} }
} }
public Point getFallBackPosition() {
return fallBackPositionProvider.apply(this);
}
public ToolTipContext setFallBackPositionProvider(final Function<ToolTipContext, Point> fallBackPositionProvider) {
this.fallBackPositionProvider = fallBackPositionProvider;
if (fallBackPositionProvider == null) this.fallBackPositionProvider = c -> null;
return this;
}
} }

43
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();
}

42
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.platform.Decorations;
import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI; import com.github.weisj.darklaf.ui.popupmenu.DarkPopupMenuUI;
import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI; import com.github.weisj.darklaf.ui.rootpane.DarkRootPaneUI;
import com.github.weisj.darklaf.ui.tooltip.DarkTooltipBorder;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
public class DarkPopupFactory extends PopupFactory { 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 @Override
public Popup getPopup(final Component owner, final Component contents, public Popup getPopup(final Component owner, final Component contents,
final int x, final int y) throws IllegalArgumentException { final int x, final int y) throws IllegalArgumentException {
Popup popup = super.getPopup(owner, contents, x, y); Popup popup = super.getPopup(owner, contents, x, y);
boolean isMediumWeight = popup.getClass().getSimpleName().endsWith("MediumWeightPopup"); boolean isMediumWeight = popup.getClass().getSimpleName().endsWith("MediumWeightPopup");
boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup"); boolean isLightWeight = popup.getClass().getSimpleName().endsWith("LightWeightPopup");
boolean isBalloonTooltip = contents instanceof JToolTip boolean forceHeavy = contents instanceof JComponent
&& ((JToolTip) contents).getBorder() instanceof DarkTooltipBorder; && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_FORCE_HEAVYWEIGHT));
boolean isPopupMenu = contents instanceof JPopupMenu; boolean isFocusable = contents instanceof JComponent
if (isMediumWeight || isLightWeight) { && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_FOCUSABLE_POPUP));
if (isBalloonTooltip) { boolean startHidden = contents instanceof JComponent
// null owner forces a heavyweight popup. && Boolean.TRUE.equals(((JComponent) contents).getClientProperty(KEY_START_HIDDEN));
popup = super.getPopup(null, contents, x, y); if (forceHeavy && (isMediumWeight || isLightWeight)) {
} else if (isMediumWeight) { // null owner forces a heavyweight popup.
JRootPane rootPane = SwingUtilities.getRootPane(contents); popup = super.getPopup(null, contents, x, y);
// Prevents decorations from being reinstalled. }
if (rootPane != null) rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT, true); 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] // Sometimes the background is java.awt.SystemColor[i=7]
// It results in a flash of white background, that is repainted with // 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) { if (window instanceof RootPaneContainer) {
JRootPane rootPane = ((RootPaneContainer) window).getRootPane(); JRootPane rootPane = ((RootPaneContainer) window).getRootPane();
rootPane.putClientProperty(DarkRootPaneUI.KEY_IS_POPUP, true); 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) { if (install) {
Decorations.installPopupWindow(window); Decorations.installPopupWindow(window);
} else { } else {
Decorations.uninstallPopupWindow(window); Decorations.uninstallPopupWindow(window);
} }
if (isBalloonTooltip || isPopupMenu) { if (startHidden) {
if (isPopupMenu) ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true); ((JComponent) contents).putClientProperty(DarkPopupMenuUI.KEY_MAKE_VISIBLE, true);
window.setOpacity(0.0f); window.setOpacity(0.0f);
} }
if (isFocusable && !window.getFocusableWindowState()) {
window.dispose();
window.setFocusableWindowState(true);
}
} }
return popup; return popup;
} }

10
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorPreviewComponent.java

@ -29,17 +29,17 @@ import java.awt.*;
/** /**
* @author Jannis Weis * @author Jannis Weis
*/ */
final class ColorPreviewComponent extends JComponent { public class ColorPreviewComponent extends JComponent {
protected final Color borderColor; protected final Color borderColor;
private Color myColor; private Color color;
ColorPreviewComponent() { public ColorPreviewComponent() {
setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
borderColor = UIManager.getColor("ColorChooser.previewBorderColor"); borderColor = UIManager.getColor("ColorChooser.previewBorderColor");
} }
public void setColor(final Color c) { public void setColor(final Color c) {
myColor = c; color = c;
repaint(); repaint();
} }
@ -54,7 +54,7 @@ final class ColorPreviewComponent extends JComponent {
g.setColor(Color.WHITE); g.setColor(Color.WHITE);
g.fillRect(i.left, i.top, width, height); 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.fillRect(i.left + 1, i.top + 1, width - 2, height - 2);
g.setColor(borderColor); g.setColor(borderColor);

678
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<ColorListener> 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
}
}

13
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, public static ColorValueFormatter init(final DarkColorModel model, final int index,
final boolean hex, final JFormattedTextField text) { final boolean hex, final JFormattedTextField text) {
ColorValueFormatter formatter = new ColorValueFormatter(model, index, hex); ColorValueFormatter formatter = new ColorValueFormatter(model, index, hex);
formatter.setText(text);
text.setFormatterFactory(new DefaultFormatterFactory(formatter)); text.setFormatterFactory(new DefaultFormatterFactory(formatter));
text.setMinimumSize(text.getPreferredSize()); text.setMinimumSize(text.getPreferredSize());
text.addFocusListener(formatter); text.addFocusListener(formatter);
return formatter; return formatter;
} }
public void setText(final JFormattedTextField text) {
this.text = text;
}
protected void error() { protected void error() {
text.putClientProperty("JTextComponent.hasError", true); text.putClientProperty("JTextComponent.hasError", true);
text.repaint(); text.repaint();
@ -213,7 +218,9 @@ public final class ColorValueFormatter extends JFormattedTextField.AbstractForma
checkRange(g, 0, 255); checkRange(g, 0, 255);
int b = Integer.valueOf(hexStr.substring(4, 6), 16); int b = Integer.valueOf(hexStr.substring(4, 6), 16);
checkRange(b, 0, 255); 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); checkRange(alpha, 0, 255);
return new Color(r, g, b, alpha); return new Color(r, g, b, alpha);
} else { } else {

223
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheel.java

@ -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<ColorListener> 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;
}
}

109
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelImageProducer.java

@ -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;
}
}

49
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/ColorWheelPanel.java

@ -24,8 +24,9 @@
package com.github.weisj.darklaf.ui.colorchooser; package com.github.weisj.darklaf.ui.colorchooser;
import com.github.weisj.darklaf.color.DarkColorModel;
import javax.swing.*; import javax.swing.*;
import javax.swing.colorchooser.AbstractColorChooserPanel;
import java.awt.*; import java.awt.*;
/** /**
@ -33,62 +34,42 @@ import java.awt.*;
* @author Konstantin Bulenkov * @author Konstantin Bulenkov
*/ */
public class ColorWheelPanel extends JPanel { public class ColorWheelPanel extends JPanel {
private final ColorWheel colorWheel; private final ColorTriangle colorWheel;
private final SlideComponent brightnessSlider;
private SlideComponent opacitySlider = null; private SlideComponent opacitySlider = null;
private boolean enableOpacity; private boolean enableOpacity;
public ColorWheelPanel(final ColorListener colorListener, final boolean enableOpacity, public ColorWheelPanel(final boolean enableOpacity, final boolean opacityInPercent) {
final boolean opacityInPercent) {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
this.enableOpacity = enableOpacity; this.enableOpacity = enableOpacity;
colorWheel = new ColorWheel(); colorWheel = new ColorTriangle();
add(colorWheel, BorderLayout.CENTER); 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) { if (enableOpacity) {
opacitySlider = new SlideComponent("Opacity", false, true); opacitySlider = new SlideComponent("Opacity", false, true);
opacitySlider.setToolTipText("Opacity"); opacitySlider.setToolTipText("Opacity");
opacitySlider.setUnits(opacityInPercent ? SlideComponent.Unit.PERCENT : SlideComponent.Unit.LEVEL); opacitySlider.setUnits(opacityInPercent ? SlideComponent.Unit.PERCENT : SlideComponent.Unit.LEVEL);
opacitySlider.addListener(integer -> { opacitySlider.addListener(integer -> {
colorWheel.setOpacity(integer); colorWheel.setOpacity(integer / 255.0);
colorWheel.repaint(); ColorWheelPanel.this.repaint();
}); });
add(opacitySlider, BorderLayout.SOUTH); add(opacitySlider, BorderLayout.SOUTH);
colorWheel.addListener(opacitySlider); colorWheel.addListener(opacitySlider);
} }
} }
public void setColor(final Color color, final Object source) { public void addListener(final ColorListener listener) {
float[] hsb = new float[3]; colorWheel.addListener(listener);
Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb); }
brightnessSlider.setValue(255 - (int) (hsb[2] * 255));
brightnessSlider.repaint();
colorWheel.dropImage(); public void setColor(final Color color, final Object source) {
if (opacitySlider != null && source instanceof AbstractColorChooserPanel) { if (opacitySlider != null) {
opacitySlider.setValue(color.getAlpha()); opacitySlider.setValue(color.getAlpha());
opacitySlider.repaint(); opacitySlider.repaint();
} }
colorWheel.setColor(source, color);
colorWheel.setColor(color, source, hsb[0], hsb[1], hsb[2]);
} }
public boolean isColorTransparencySelectionEnabled() { public boolean isColorTransparencySelectionEnabled() {
@ -102,4 +83,8 @@ public class ColorWheelPanel extends JPanel {
opacitySlider.setVisible(b); opacitySlider.setVisible(b);
} }
} }
public void setModel(final DarkColorModel darkColorModel) {
colorWheel.setColorModel(darkColorModel);
}
} }

379
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.DefaultColorPipette;
import com.github.weisj.darklaf.components.uiresource.JButtonUIResource; import com.github.weisj.darklaf.components.uiresource.JButtonUIResource;
import com.github.weisj.darklaf.decorators.AncestorAdapter; 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.ui.button.DarkButtonUI;
import com.github.weisj.darklaf.util.ColorUtil; import com.github.weisj.darklaf.util.ColorUtil;
import com.github.weisj.darklaf.util.PropertyKey;
import javax.swing.*; import javax.swing.*;
import javax.swing.colorchooser.AbstractColorChooserPanel; import javax.swing.colorchooser.AbstractColorChooserPanel;
import javax.swing.colorchooser.ColorSelectionModel; import javax.swing.colorchooser.ColorSelectionModel;
import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -48,62 +46,63 @@ import java.awt.event.KeyEvent;
*/ */
public class DarkColorChooserPanel extends AbstractColorChooserPanel implements ColorListener { public class DarkColorChooserPanel extends AbstractColorChooserPanel implements ColorListener {
public static final String TRANSPARENCY_ENABLED_PROPERTY public static final String TRANSPARENCY_ENABLED_PROPERTY = "transparency";
= "TransparencyEnabled";
private final Icon pipetteIcon;
private final Icon pipetteHoverIcon;
private final ColorPipette pipette; private final ColorPipette pipette;
private final ColorWheelPanel colorWheelPanel; private final ColorWheelPanel colorWheelPanel;
private final JFormattedTextField textHex; private final ColorPreviewComponent previewComponent;
private final ColorValueFormatter hexFormatter;
private final JFormattedTextField[] valueFields; private JFormattedTextField[] valueFields;
private final ColorValueFormatter[] formatters; private ColorValueFormatter[] formatters;
private JLabel[] descriptors;
private JLabel[] descriptorsAfter;
private JFormattedTextField textHex;
private ColorValueFormatter hexFormatter;
private final JComboBox<DarkColorModel> formatBox; private final JComboBox<DarkColorModel> formatBox;
private final ColorPreviewComponent previewComponent;
private final JLabel[] descriptors;
private final JLabel[] descriptorsAfter;
private final boolean doneInit;
private Color currentColor; private Color currentColor;
private boolean isChanging;
private Icon pipetteIcon;
private Icon pipetteHoverIcon;
protected boolean isChanging;
public DarkColorChooserPanel(final DarkColorModel... colorModels) { public DarkColorChooserPanel(final DarkColorModel... colorModels) {
if (colorModels == null || colorModels.length == 0) { if (colorModels == null || colorModels.length == 0) {
throw new IllegalArgumentException("Must pass at least one valid colorModel"); throw new IllegalArgumentException("Must pass at least one valid colorModel");
} }
isChanging = true;
previewComponent = new ColorPreviewComponent(); previewComponent = new ColorPreviewComponent();
colorWheelPanel = new ColorWheelPanel(this, true, true); colorWheelPanel = new ColorWheelPanel(true, true);
pipette = new DefaultColorPipette(this, colorWheelPanel::setColor); pipette = new DefaultColorPipette(this, colorWheelPanel::setColor);
pipetteIcon = UIManager.getIcon("ColorChooser.pipette.icon"); pipetteIcon = UIManager.getIcon("ColorChooser.pipette.icon");
pipetteHoverIcon = UIManager.getIcon("ColorChooser.pipetteRollover.icon"); pipetteHoverIcon = UIManager.getIcon("ColorChooser.pipetteRollover.icon");
formatBox = new JComboBox<>(colorModels); formatBox = createColorFormatChooser(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.setPrototypeDisplayValue(prototype); initInputFields(colorModels);
descriptors = new JLabel[record];
descriptorsAfter = new JLabel[record];
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() { textHex.addAncestorListener(new AncestorAdapter() {
@Override @Override
public void ancestorAdded(final AncestorEvent event) { public void ancestorAdded(final AncestorEvent event) {
@ -111,6 +110,83 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
textHex.removeAncestorListener(this); 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 = ColorValueFormatter.init(getDarkColorModel(), 0, true, textHex);
hexFormatter.setTransparencyEnabled(isColorTransparencySelectionEnabled()); hexFormatter.setTransparencyEnabled(isColorTransparencySelectionEnabled());
@ -124,15 +200,34 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
valueFields[i] = createColorField(false); valueFields[i] = createColorField(false);
formatters[i] = ColorValueFormatter.init(getDarkColorModel(), i, false, valueFields[i]); formatters[i] = ColorValueFormatter.init(getDarkColorModel(), i, false, valueFields[i]);
} }
}
setLayout(new BorderLayout()); private JFormattedTextField createColorField(final boolean hex) {
setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); JFormattedTextField field = new JFormattedTextField(0);
add(buildTopPanel(UIManager.getBoolean("ColorChooser.pipetteEnabled")), BorderLayout.NORTH); field.setColumns(hex ? 8 : 3);
add(colorWheelPanel, BorderLayout.CENTER); field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
add(Box.createVerticalStrut(10), BorderLayout.SOUTH); return field;
updateValueFields(); }
updateDescriptors();
doneInit = true; protected int getMaxFieldCount(final DarkColorModel[] colorModels) {
int record = 0;
for (DarkColorModel model : colorModels) {
record = Math.max(model.getCount(), record);
}
return record;
}
protected JComboBox<DarkColorModel> createColorFormatChooser(final DarkColorModel[] colorModels) {
JComboBox<DarkColorModel> 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() { private void updateDescriptors() {
@ -152,9 +247,9 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
} }
} }
private void updateValueFields() { private void toggleValueFields() {
DarkColorModel model = getDarkColorModel(); DarkColorModel model = getDarkColorModel();
int count = model.getValueCount(); int count = model.getCount();
for (int i = 0; i < valueFields.length; i++) { for (int i = 0; i < valueFields.length; i++) {
valueFields[i].setEnabled(i < count); valueFields[i].setEnabled(i < count);
valueFields[i].setVisible(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 @Override
protected Color getColorFromModel() { protected Color getColorFromModel() {
Color c = super.getColorFromModel(); Color c = super.getColorFromModel();
return c == null ? currentColor : c; return c == null ? currentColor : c;
} }
protected DarkColorModel getDarkColorModel() { protected DarkColorModel getDarkColorModel() {
return (DarkColorModel) formatBox.getSelectedItem(); return (DarkColorModel) formatBox.getSelectedItem();
} }
@ -193,22 +279,7 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
final JPanel previewPanel = new JPanel(new BorderLayout()); final JPanel previewPanel = new JPanel(new BorderLayout());
if (enablePipette && pipette != null) { if (enablePipette && pipette != null) {
JButton pipetteButton = new JButtonUIResource(); previewPanel.add(createPipetteButton(), BorderLayout.WEST);
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(previewComponent, BorderLayout.CENTER); previewPanel.add(previewComponent, BorderLayout.CENTER);
result.add(previewPanel, BorderLayout.NORTH); result.add(previewPanel, BorderLayout.NORTH);
@ -242,51 +313,39 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
return result; 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) { public boolean isColorTransparencySelectionEnabled() {
JFormattedTextField field = new JFormattedTextField(0); return colorWheelPanel.isColorTransparencySelectionEnabled();
field.setColumns(hex ? 8 : 4); }
if (!hex) {
field.addPropertyChangeListener(e -> { public void setColorTransparencySelectionEnabled(final boolean b) {
if (PropertyKey.VALUE.equals(e.getPropertyName())) { boolean oldValue = isColorTransparencySelectionEnabled();
updatePreviewFromTextFields(); if (b != oldValue) {
} hexFormatter.setTransparencyEnabled(b);
}); colorWheelPanel.setColorTransparencySelectionEnabled(b);
} else { if (b && getColorFromModel().getAlpha() < 255) {
field.getDocument().addDocumentListener(new DocumentListener() { colorChanged(ColorUtil.removeAlpha(getColorFromModel()), this);
@Override }
public void insertUpdate(final DocumentEvent e) { firePropertyChange(TRANSPARENCY_ENABLED_PROPERTY, oldValue, b);
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) {
}
}
});
} }
field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT);
return field;
} }
protected Icon getPipetteIcon() { protected Icon getPipetteIcon() {
@ -304,15 +363,6 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
@Override @Override
public void updateChooser() { public void updateChooser() {
if (isChanging) return;
Color color = getColorFromModel();
if (color != null) {
colorWheelPanel.setColor(color, this);
}
}
@Override
protected void buildChooser() {
} }
@Override @Override
@ -335,83 +385,8 @@ public class DarkColorChooserPanel extends AbstractColorChooserPanel implements
return null; return null;
} }
public boolean isColorTransparencySelectionEnabled() {
return colorWheelPanel.isColorTransparencySelectionEnabled();
}
@Override @Override
public Icon getLargeDisplayIcon() { public Icon getLargeDisplayIcon() {
return null; 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;
}
} }

11
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/DarkColorChooserUI.java

@ -23,9 +23,10 @@
*/ */
package com.github.weisj.darklaf.ui.colorchooser; 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.DarkColorModelCMYK;
import com.github.weisj.darklaf.color.DarkColorModelHSB;
import com.github.weisj.darklaf.color.DarkColorModelHSL; 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.decorators.AncestorAdapter;
import com.github.weisj.darklaf.util.PropertyKey; import com.github.weisj.darklaf.util.PropertyKey;
@ -80,10 +81,10 @@ public class DarkColorChooserUI extends BasicColorChooserUI {
@Override @Override
protected AbstractColorChooserPanel[] createDefaultChoosers() { protected AbstractColorChooserPanel[] createDefaultChoosers() {
return new AbstractColorChooserPanel[]{ return new AbstractColorChooserPanel[]{
new DarkColorChooserPanel(new DarkColorModel(), new DarkColorChooserPanel(DarkColorModelRGB.getInstance(),
new DarkColorModelHSL(), DarkColorModelHSB.getInstance(),
// new DarkColorModelHSB(), DarkColorModelHSL.getInstance(),
new DarkColorModelCMYK()), DarkColorModelCMYK.getInstance()),
new DarkSwatchesChooserPanel(), new DarkSwatchesChooserPanel(),
}; };
} }

44
core/src/main/java/com/github/weisj/darklaf/ui/colorchooser/SlideComponent.java

@ -108,7 +108,6 @@ class SlideComponent extends JComponent implements ColorListener {
@Override @Override
public void componentResized(final ComponentEvent e) { public void componentResized(final ComponentEvent e) {
setValue(getValue()); setValue(getValue());
fireValueChanged();
repaint(); repaint();
} }
}); });
@ -160,8 +159,9 @@ class SlideComponent extends JComponent implements ColorListener {
} }
public void setValue(final int value) { public void setValue(final int value) {
if (value < 0 || value > 255) { if (value < Unit.LEVEL.getMin() || value > Unit.LEVEL.getMax()) {
throw new IllegalArgumentException("Value " + value + " not in range [0,255]"); throw new IllegalArgumentException(
"Value " + value + " not in range [" + Unit.LEVEL.getMin() + "," + Unit.LEVEL.getMax() + "]");
} }
pointerValue = valueToPointerValue(value); pointerValue = valueToPointerValue(value);
this.value = value; this.value = value;
@ -292,23 +292,45 @@ class SlideComponent extends JComponent implements ColorListener {
} }
enum Unit { enum Unit {
PERCENT, LEVEL(0, 255f) {
LEVEL; @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) { private static String formatValue(final int value, final Unit unit) {
if (unit == PERCENT) { 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 { } 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) { public float getMin() {
return LEVEL.equals(unit) ? LEVEL_MAX_VALUE : PERCENT_MAX_VALUE; return min;
} }
} }
} }

7
core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java

@ -23,6 +23,7 @@
*/ */
package com.github.weisj.darklaf.ui.popupmenu; package com.github.weisj.darklaf.ui.popupmenu;
import com.github.weisj.darklaf.ui.DarkPopupFactory;
import sun.awt.SunToolkit; import sun.awt.SunToolkit;
import javax.swing.*; import javax.swing.*;
@ -86,6 +87,12 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
return mouseGrabber; return mouseGrabber;
} }
@Override
public void installDefaults() {
super.installDefaults();
popupMenu.putClientProperty(DarkPopupFactory.KEY_START_HIDDEN, true);
}
@Override @Override
protected void installListeners() { protected void installListeners() {
super.installListeners(); super.installListeners();

1
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."; protected static final String KEY_PREFIX = "JRootPane.";
public static final String KEY_IS_MEDIUM_WEIGHT_POPUP_ROOT = "mediumWeightPopupRoot"; 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_POPUP = KEY_PREFIX + "isPopup";
public static final String KEY_IS_TOOLTIP = KEY_PREFIX + "isToolTip";
private Window window; private Window window;
private CustomTitlePane titlePane; private CustomTitlePane titlePane;
private LayoutManager layoutManager; private LayoutManager layoutManager;

2
core/src/main/java/com/github/weisj/darklaf/ui/slider/DarkSliderUI.java

@ -167,7 +167,7 @@ public class DarkSliderUI extends BasicSliderUI implements PropertyChangeListene
} }
@Override @Override
protected Dimension getThumbSize() { public Dimension getThumbSize() {
if (isPlainThumb()) { if (isPlainThumb()) {
return new Dimension(plainThumbRadius + 6, plainThumbRadius + 6); return new Dimension(plainThumbRadius + 6, plainThumbRadius + 6);
} }

7
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, private final DropShadowBorder shadowBorder = new DropShadowBorder(Color.BLACK, shadowSize, opacity, cornerSize,
false, true, true, true); false, true, true, true);
private final BubbleBorder bubbleBorder; private final BubbleBorder bubbleBorder;
private boolean skipShadow;
public DarkTooltipBorder() { public DarkTooltipBorder() {
bubbleBorder = new BubbleBorder(UIManager.getColor("ToolTip.borderColor")); 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, Area bubbleArea = bubbleBorder.getInnerArea(x + ins.left, y + ins.top,
width - ins.left - ins.right, width - ins.left - ins.right,
height - ins.top - ins.bottom); height - ins.top - ins.bottom);
if (UIManager.getBoolean("ToolTip.paintShadow")) { if (!skipShadow && UIManager.getBoolean("ToolTip.paintShadow")) {
paintShadow(c, g, x, y, width, height, bubbleArea); paintShadow(c, g, x, y, width, height, bubbleArea);
} }
bubbleBorder.paintBorder(g, bubbleArea); bubbleBorder.paintBorder(g, bubbleArea);
@ -185,4 +186,8 @@ public class DarkTooltipBorder implements Border {
if (isPlain(c)) return 0; if (isPlain(c)) return 0;
return shadowBorder.getShadowSize(); return shadowBorder.getShadowSize();
} }
public void setSkipShadow(final boolean skip) {
this.skipShadow = skip;
}
} }

24
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/DarkTooltipUI.java

@ -24,7 +24,7 @@
package com.github.weisj.darklaf.ui.tooltip; package com.github.weisj.darklaf.ui.tooltip;
import com.github.weisj.darklaf.components.tooltip.ToolTipStyle; 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 com.github.weisj.darklaf.util.*;
import javax.swing.*; import javax.swing.*;
@ -115,6 +115,9 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
@Override @Override
protected void installDefaults(final JComponent c) { protected void installDefaults(final JComponent c) {
super.installDefaults(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(); fadeAnimator = new FadeInAnimator();
c.setOpaque(false); c.setOpaque(false);
DarkTooltipBorder border = new DarkTooltipBorder(); DarkTooltipBorder border = new DarkTooltipBorder();
@ -202,14 +205,14 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
((JComponent) toolTip.getParent()).setOpaque(false); ((JComponent) toolTip.getParent()).setOpaque(false);
} }
if (lastRootPane != null) { 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))) { if (w != null && !isDecorated(w) && (w.getClass().getEnclosingClass().equals(Popup.class))) {
w.setBackground(DarkUIUtil.TRANSPARENT_COLOR); w.setBackground(DarkUIUtil.TRANSPARENT_COLOR);
if (w instanceof RootPaneContainer) { if (w instanceof RootPaneContainer) {
lastRootPane = ((RootPaneContainer) w).getRootPane(); lastRootPane = ((RootPaneContainer) w).getRootPane();
if (lastRootPane != null) { 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() { 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 @Override
@ -359,13 +363,23 @@ public class DarkTooltipUI extends BasicToolTipUI implements PropertyChangeListe
alpha = ((float) frame * MAX_ALPHA) / totalFrames; alpha = ((float) frame * MAX_ALPHA) / totalFrames;
Window window = SwingUtilities.getWindowAncestor(toolTip); Window window = SwingUtilities.getWindowAncestor(toolTip);
if (window != null) window.setOpacity(alpha); if (window != null) window.setOpacity(alpha);
Border border = toolTip.getBorder();
if (border instanceof DarkTooltipBorder) {
((DarkTooltipBorder) border).setSkipShadow(false);
}
} }
@Override @Override
protected void paintCycleEnd() { protected void paintCycleEnd() {
alpha = MAX_ALPHA; alpha = MAX_ALPHA;
Window window = SwingUtilities.getWindowAncestor(toolTip); 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);
}
}
} }
} }
} }

14
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java

@ -43,7 +43,7 @@ public class ToolTipUtil {
JComponent target = toolTip.getComponent(); JComponent target = toolTip.getComponent();
if (target == null) return; if (target == null) return;
ToolTipContext context = getToolTipContext(target); ToolTipContext context = getToolTipContext(toolTip);
if (context == null) return; if (context == null) return;
context.setTarget(target); context.setTarget(target);
@ -92,6 +92,7 @@ public class ToolTipUtil {
if (pos == null) { if (pos == null) {
context.setAlignment(Alignment.CENTER); context.setAlignment(Alignment.CENTER);
context.setCenterAlignment(Alignment.CENTER); context.setCenterAlignment(Alignment.CENTER);
pos = context.getFallBackPosition();
} }
context.updateToolTip(); context.updateToolTip();
context.setAlignment(original); context.setAlignment(original);
@ -137,12 +138,16 @@ public class ToolTipUtil {
&& SwingUtilities.isRectangleContainingRectangle(screenBoundary, toolTipBounds); && SwingUtilities.isRectangleContainingRectangle(screenBoundary, toolTipBounds);
} }
protected static ToolTipContext getToolTipContext(final JComponent comp) { protected static ToolTipContext getToolTipContext(final JToolTip tooltip) {
Object context = comp.getClientProperty(DarkTooltipUI.KEY_CONTEXT); Object context = tooltip.getClientProperty(DarkTooltipUI.KEY_CONTEXT);
if (context instanceof ToolTipContext) { if (context instanceof ToolTipContext) {
return (ToolTipContext) context; 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))) { if (ToolTipStyle.BALLOON.equals(DarkTooltipUI.getStyle(style))) {
return DEFAULT_CONTEXT; return DEFAULT_CONTEXT;
} }
@ -183,7 +188,6 @@ public class ToolTipUtil {
return config; return config;
} }
} }
return null; return null;
} }
} }

32
core/src/main/java/com/github/weisj/darklaf/util/GraphicsContext.java

@ -63,12 +63,40 @@ public class GraphicsContext {
} }
public void restore() { public void restore() {
graphics2D.setRenderingHints(this.hintsMap); restoreRenderingHints();
restoreComposite();
restoreStroke();
restoreColor();
restorePaint();
restoreFont();
restoreClip();
}
public void restoreComposite() {
graphics2D.setComposite(composite); graphics2D.setComposite(composite);
}
public void restoreFont() {
graphics2D.setFont(font);
}
public void restoreRenderingHints() {
graphics2D.setRenderingHints(this.hintsMap);
}
public void restoreStroke() {
graphics2D.setStroke(stroke); graphics2D.setStroke(stroke);
}
public void restoreColor() {
graphics2D.setColor(color); graphics2D.setColor(color);
}
public void restorePaint() {
graphics2D.setPaint(paint); graphics2D.setPaint(paint);
graphics2D.setFont(font); }
public void restoreClip() {
graphics2D.setClip(clip); graphics2D.setClip(clip);
} }
} }

3
core/src/main/resources/com/github/weisj/darklaf/properties/ui/colorChooser.properties

@ -39,6 +39,9 @@ ColorChooser.sliderShadow = %shadow
ColorChooser.errorDelay = 600 ColorChooser.errorDelay = 600
ColorChooser.swatchesDefaultRecentColor = %background ColorChooser.swatchesDefaultRecentColor = %background
ColorChooser.colorWheelBackground = %background ColorChooser.colorWheelBackground = %background
ColorChooser.outerIndicatorRadius = 3
ColorChooser.innerIndicatorRadius = 3
#Icons #Icons
ColorChooser.pipette.icon = misc/pipette.svg[themed] ColorChooser.pipette.icon = misc/pipette.svg[themed]
ColorChooser.pipetteRollover.icon = misc/pipetteRollover.svg[themed] ColorChooser.pipetteRollover.icon = misc/pipetteRollover.svg[themed]

28
core/src/test/java/ui/QuickColorChooser.java

@ -23,10 +23,13 @@
*/ */
package ui; package ui;
import com.github.weisj.darklaf.components.color.PopupColorChooser;
import com.github.weisj.darklaf.decorators.MouseClickListener; import com.github.weisj.darklaf.decorators.MouseClickListener;
import com.github.weisj.darklaf.icons.EmptyIcon;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -42,21 +45,28 @@ public class QuickColorChooser extends JPanel {
public QuickColorChooser(final String title, final Color color, final BiConsumer<Boolean, Color> onStatusChange, public QuickColorChooser(final String title, final Color color, final BiConsumer<Boolean, Color> onStatusChange,
final boolean showCheckBox) { final boolean showCheckBox) {
super(new FlowLayout(FlowLayout.LEFT, 0, 0)); super(new FlowLayout(FlowLayout.LEFT, 0, 0));
icon = new SolidColorIcon(color);
JLabel label = new JLabel(title, icon, JLabel.LEFT);
checkBox = new JCheckBox(); checkBox = new JCheckBox();
if (showCheckBox) { if (showCheckBox) {
checkBox.addActionListener(e -> onStatusChange.accept(isSelected(), getColor())); checkBox.addActionListener(e -> onStatusChange.accept(isSelected(), getColor()));
add(checkBox); add(checkBox);
} }
label.addMouseListener((MouseClickListener) e -> {
Color c = JColorChooser.showDialog(QuickColorChooser.this, title, icon.getColor()); icon = new SolidColorIcon(color);
if (c != null) { JLabel colorLabel = new JLabel(icon, JLabel.LEFT);
onStatusChange.accept(isSelected(), c); AtomicBoolean isShowing = new AtomicBoolean(false);
icon.setColor(c); 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<Boolean, Color> onStatusChange) { public QuickColorChooser(final String title, final Color color, final BiConsumer<Boolean, Color> onStatusChange) {

45
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";
}
}

6
core/src/test/java/ui/text/TextFieldDemo.java

@ -38,7 +38,7 @@ public class TextFieldDemo implements ComponentDemo {
@Override @Override
public JComponent createComponent() { public JComponent createComponent() {
JTextField textField = new JTextField("Demo TextField"); JTextField textField = createTextField();
DemoPanel panel = new DemoPanel(textField); DemoPanel panel = new DemoPanel(textField);
JPanel controlPanel = panel.addControls(); JPanel controlPanel = panel.addControls();
@ -68,6 +68,10 @@ public class TextFieldDemo implements ComponentDemo {
return panel; return panel;
} }
protected JTextField createTextField() {
return new JTextField("Demo TextField");
}
@Override @Override
public String getTitle() { public String getTitle() {
return "TextField Demo"; return "TextField Demo";

Loading…
Cancel
Save