mirror of https://github.com/weisJ/darklaf.git
Browse Source
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
30 changed files with 1730 additions and 752 deletions
@ -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]); |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
@ -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(); |
||||
} |
@ -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 |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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"; |
||||
} |
||||
} |
Loading…
Reference in new issue