Add RuneliteColorPicker

Co-authored-by: psikoi <ruben.amendoeira@gmail.com>
This commit is contained in:
Ron Young
2019-01-03 11:06:10 -06:00
parent cbb14b7e1a
commit 2a76a23548
8 changed files with 1062 additions and 26 deletions

View File

@@ -60,6 +60,7 @@ import net.runelite.api.events.ConfigChanged;
import net.runelite.client.RuneLite;
import net.runelite.client.account.AccountSession;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.util.ColorUtil;
import net.runelite.http.api.config.ConfigClient;
import net.runelite.http.api.config.ConfigEntry;
import net.runelite.http.api.config.Configuration;
@@ -482,7 +483,7 @@ public class ConfigManager
}
if (type == Color.class)
{
return Color.decode(str);
return ColorUtil.fromString(str);
}
if (type == Dimension.class)
{

View File

@@ -48,11 +48,8 @@ import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
@@ -63,6 +60,7 @@ import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
@@ -90,6 +88,8 @@ import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.ComboBoxListRenderer;
import net.runelite.client.ui.components.IconButton;
import net.runelite.client.ui.components.IconTextField;
import net.runelite.client.ui.components.colorpicker.RuneliteColorPicker;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
@@ -400,47 +400,48 @@ public class ConfigPanel extends PluginPanel
String existing = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName());
Color existingColor;
JButton colorPicker;
JButton colorPickerBtn;
if (existing == null)
{
existingColor = Color.BLACK;
colorPicker = new JButton("Pick a color");
colorPickerBtn = new JButton("Pick a color");
}
else
{
existingColor = Color.decode(existing);
colorPicker = new JButton("#" + Integer.toHexString(existingColor.getRGB()).substring(2).toUpperCase());
existingColor = ColorUtil.fromString(existing);
colorPickerBtn = new JButton(ColorUtil.toHexColor(existingColor).toUpperCase());
}
colorPicker.setFocusable(false);
colorPicker.setBackground(existingColor);
colorPicker.addMouseListener(new MouseAdapter()
colorPickerBtn.setFocusable(false);
colorPickerBtn.setBackground(existingColor);
colorPickerBtn.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
final JFrame parent = new JFrame();
JColorChooser jColorChooser = new JColorChooser(existingColor);
jColorChooser.getSelectionModel().addChangeListener(e1 ->
RuneliteColorPicker colorPicker = new RuneliteColorPicker(SwingUtilities.windowForComponent(ConfigPanel.this),
colorPickerBtn.getBackground(), cid.getItem().name(), cid.getAlpha() == null);
colorPicker.setLocation(getLocationOnScreen());
colorPicker.setOnColorChange(c ->
{
colorPicker.setBackground(jColorChooser.getColor());
colorPicker.setText("#" + Integer.toHexString(jColorChooser.getColor().getRGB()).substring(2).toUpperCase());
colorPickerBtn.setBackground(c);
colorPickerBtn.setText(ColorUtil.toHexColor(c).toUpperCase());
});
parent.addWindowListener(new WindowAdapter()
colorPicker.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
changeConfiguration(listItem, config, jColorChooser, cd, cid);
changeConfiguration(listItem, config, colorPicker, cd, cid);
}
});
parent.add(jColorChooser);
parent.pack();
parent.setVisible(true);
colorPicker.setVisible(true);
}
});
item.add(colorPicker, BorderLayout.EAST);
item.add(colorPickerBtn, BorderLayout.EAST);
}
if (cid.getType() == Dimension.class)
@@ -556,7 +557,7 @@ public class ConfigPanel extends PluginPanel
scrollPane.getVerticalScrollBar().setValue(0);
}
private void changeConfiguration(PluginListItem listItem, Config config, JComponent component, ConfigDescriptor cd, ConfigItemDescriptor cid)
private void changeConfiguration(PluginListItem listItem, Config config, Component component, ConfigDescriptor cd, ConfigItemDescriptor cid)
{
final ConfigItem configItem = cid.getItem();
@@ -588,10 +589,10 @@ public class ConfigPanel extends PluginPanel
JTextComponent textField = (JTextComponent) component;
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), textField.getText());
}
else if (component instanceof JColorChooser)
else if (component instanceof RuneliteColorPicker)
{
JColorChooser jColorChooser = (JColorChooser) component;
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), String.valueOf(jColorChooser.getColor().getRGB()));
RuneliteColorPicker colorPicker = (RuneliteColorPicker) component;
configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), colorPicker.getSelectedColor().getRGB() + "");
}
else if (component instanceof JComboBox)
{

View File

@@ -0,0 +1,207 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import com.google.common.primitives.Ints;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.util.function.Consumer;
import javax.swing.JPanel;
import lombok.Setter;
public class ColorPanel extends JPanel
{
private static final int SELECTOR_RADIUS = 7;
private final int size;
private BufferedImage image;
private Point targetPosition;
private int selectedY;
private boolean forceRedraw;
@Setter
private Consumer<Color> onColorChange;
ColorPanel(int size)
{
this.size = size;
this.image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
this.targetPosition = new Point(size, 0);
setPreferredSize(new Dimension(size, size));
addMouseMotionListener(new MouseMotionAdapter()
{
@Override
public void mouseDragged(MouseEvent me)
{
moveTarget(me.getX(), me.getY(), true);
}
});
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent me)
{
moveTarget(me.getX(), me.getY(), true);
}
@Override
public void mousePressed(MouseEvent me)
{
moveTarget(me.getX(), me.getY(), true);
}
});
}
/*
* Sets the gradient's base hue index.
*/
void setBaseColor(int selectedY)
{
if (this.selectedY == selectedY)
{
return;
}
this.selectedY = selectedY;
redrawGradient();
if (onColorChange != null)
{
onColorChange.accept(colorAt(targetPosition.x, targetPosition.y));
}
paintImmediately(0, 0, size, size);
}
/*
* Move the indicator to the closest color without firing change event.
*/
void moveToClosestColor(int y, Color color)
{
Point closest = closestPointToColor(color);
if (this.selectedY == y && closest.x == targetPosition.x && closest.y == targetPosition.y)
{
return;
}
this.selectedY = y;
redrawGradient();
moveTarget(closest.x, closest.y, false);
}
/*
* Calculates the closest point to a given color.
*/
private Point closestPointToColor(Color target)
{
float[] hsb = Color.RGBtoHSB(target.getRed(), target.getGreen(), target.getBlue(), null);
int offSize = size - 1;
return new Point((int) (hsb[1] * offSize), offSize - (int) (hsb[2] * offSize));
}
/**
* Moves the target (selector) to a specified x,y coordinates.
*/
private void moveTarget(int x, int y, boolean shouldUpdate)
{
if (targetPosition.x == x && targetPosition.y == y && !forceRedraw)
{
return;
}
x = Ints.constrainToRange(x, 0, size - 1);
y = Ints.constrainToRange(y, 0, size - 1);
targetPosition = new Point(x, y);
paintImmediately(0, 0, size, size);
if (onColorChange != null && shouldUpdate)
{
onColorChange.accept(colorAt(x, y));
}
forceRedraw = false;
}
@Override
public void paint(Graphics g)
{
// Paint the gradient
g.drawImage(this.image, 0, 0, null);
int targetX = targetPosition.x - (SELECTOR_RADIUS / 2);
int targetY = targetPosition.y - (SELECTOR_RADIUS / 2);
// Paint the target (selector)
g.setColor(Color.WHITE);
g.fillOval(targetX, targetY, SELECTOR_RADIUS, SELECTOR_RADIUS);
g.setColor(Color.BLACK);
g.drawOval(targetX, targetY, SELECTOR_RADIUS, SELECTOR_RADIUS);
}
/*
* Draws a 3-color gradient based on white, black, and current hue index.
*/
private void redrawGradient()
{
Color primaryRight = Color.getHSBColor(1f - this.selectedY / (float) (size - 1), 1, 1);
Graphics2D g = image.createGraphics();
GradientPaint primary = new GradientPaint(
0f, 0f, Color.WHITE,
size - 1, 0f, primaryRight);
GradientPaint shade = new GradientPaint(
0f, 0f, new Color(0, 0, 0, 0),
0f, size - 1, Color.BLACK);
g.setPaint(primary);
g.fillRect(0, 0, size, size);
g.setPaint(shade);
g.fillRect(0, 0, size, size);
g.dispose();
forceRedraw = true;
}
/*
* Determines which color is displayed on the gradient at x,y.
*/
private Color colorAt(int x, int y)
{
x = Ints.constrainToRange(x, 0, size - 1);
y = Ints.constrainToRange(y, 0, size - 1);
return new Color(image.getRGB(x, y));
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.function.Consumer;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.ColorUtil;
public class ColorValuePanel extends JPanel
{
private static final int DEFAULT_VALUE = ColorUtil.MAX_RGB_VALUE;
private ColorValueSlider slider = new ColorValueSlider();
private JTextField input = new JTextField();
private Consumer<Integer> onValueChanged;
void setOnValueChanged(Consumer<Integer> c)
{
onValueChanged = c;
slider.setOnValueChanged(c);
}
ColorValuePanel(String labelText)
{
setLayout(new BorderLayout(10, 0));
setBackground(ColorScheme.DARK_GRAY_COLOR);
input.setBackground(ColorScheme.DARKER_GRAY_COLOR);
input.setPreferredSize(new Dimension(35, 30));
input.setBorder(new EmptyBorder(5, 5, 5, 5));
((AbstractDocument) input.getDocument()).setDocumentFilter(new DocumentFilter()
{
@Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String str, AttributeSet attrs)
throws BadLocationException
{
try
{
String text = RuneliteColorPicker.getReplacedText(fb, offset, length, str);
int value = Integer.parseInt(text);
if (value < ColorUtil.MIN_RGB_VALUE || value > ColorUtil.MAX_RGB_VALUE)
{
Toolkit.getDefaultToolkit().beep();
return;
}
super.replace(fb, offset, length, str, attrs);
}
catch (NumberFormatException e)
{
Toolkit.getDefaultToolkit().beep();
}
}
});
input.addFocusListener(new FocusAdapter()
{
@Override
public void focusLost(FocusEvent e)
{
updateText();
}
});
input.addActionListener(a -> updateText());
JLabel label = new JLabel(labelText);
label.setPreferredSize(new Dimension(45, 0));
label.setForeground(Color.WHITE);
slider.setBackground(ColorScheme.DARK_GRAY_COLOR);
slider.setBorder(new EmptyBorder(0, 0, 5, 0));
slider.setPreferredSize(new Dimension(ColorUtil.MAX_RGB_VALUE + ColorValueSlider.KNOB_WIDTH, 30));
update(DEFAULT_VALUE);
add(label, BorderLayout.WEST);
add(slider, BorderLayout.CENTER);
add(input, BorderLayout.EAST);
}
private void updateText()
{
int value = Integer.parseInt(input.getText());
update(value);
if (onValueChanged != null)
{
onValueChanged.accept(getValue());
}
}
public void update(int value)
{
value = ColorUtil.constrainValue(value);
slider.setValue(value);
input.setText(value + "");
}
public int getValue()
{
return slider.getValue();
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import com.google.common.primitives.Ints;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.function.Consumer;
import javax.swing.JPanel;
import lombok.Setter;
import net.runelite.client.util.ColorUtil;
public class ColorValueSlider extends JPanel
{
static final int KNOB_WIDTH = 4;
private static final int KNOB_HEIGHT = 14;
private static final Color TRACK_COLOR = new Color(20, 20, 20);
private static final Color KNOB_COLOR = new Color(150, 150, 150);
private int value = ColorUtil.MAX_RGB_VALUE + KNOB_WIDTH;
@Setter
private Consumer<Integer> onValueChanged;
ColorValueSlider()
{
addMouseMotionListener(new MouseMotionAdapter()
{
@Override
public void mouseDragged(MouseEvent me)
{
moveTarget(me.getX(), true);
}
});
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent me)
{
moveTarget(me.getX(), true);
}
@Override
public void mousePressed(MouseEvent me)
{
moveTarget(me.getX(), true);
}
});
}
public void setValue(int x)
{
moveTarget(x + KNOB_WIDTH, false);
}
private void moveTarget(int x, boolean shouldUpdate)
{
value = Ints.constrainToRange(x, ColorUtil.MIN_RGB_VALUE + KNOB_WIDTH, ColorUtil.MAX_RGB_VALUE + KNOB_WIDTH);
paintImmediately(0, 0, this.getWidth(), this.getHeight());
if (shouldUpdate && onValueChanged != null)
{
onValueChanged.accept(getValue());
}
}
@Override
public void paint(Graphics g)
{
super.paint(g);
g.setColor(TRACK_COLOR);
g.fillRect(0, this.getHeight() / 2 - 2, this.getWidth() - KNOB_WIDTH, 5);
g.setColor(KNOB_COLOR);
g.fillRect(value - KNOB_WIDTH / 2, this.getHeight() / 2 - KNOB_HEIGHT / 2, KNOB_WIDTH, KNOB_HEIGHT);
}
int getValue()
{
return value - KNOB_WIDTH;
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import com.google.common.primitives.Ints;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.function.Consumer;
import javax.swing.JPanel;
import lombok.Getter;
import lombok.Setter;
public class HuePanel extends JPanel
{
private static final int PANEL_WIDTH = 15;
private static final int KNOB_HEIGHT = 4;
private final int height;
@Getter
private int selectedY;
@Setter
private Consumer<Integer> onColorChange;
HuePanel(int height)
{
this.height = height;
setPreferredSize(new Dimension(PANEL_WIDTH, height));
addMouseMotionListener(new MouseMotionAdapter()
{
@Override
public void mouseDragged(MouseEvent me)
{
moveSelector(me.getY());
}
});
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent me)
{
moveSelector(me.getY());
}
@Override
public void mousePressed(MouseEvent me)
{
moveSelector(me.getY());
}
});
}
/**
* Repaint slider with closest guess for y value on slider.
*/
public void select(Color color)
{
this.selectedY = closestYToColor(color);
paintImmediately(0, 0, PANEL_WIDTH, height);
}
/**
* Moves the selector to a specified y coordinate.
*/
private void moveSelector(int y)
{
y = Ints.constrainToRange(y, 0, height - 1);
if (y == this.selectedY)
{
return;
}
this.selectedY = y;
paintImmediately(0, 0, PANEL_WIDTH, height);
if (this.onColorChange != null)
{
this.onColorChange.accept(y);
}
}
/**
* Calculates a close y value for the given target color.
*/
private int closestYToColor(Color target)
{
float[] hsb = Color.RGBtoHSB(target.getRed(), target.getGreen(), target.getBlue(), null);
float hue = hsb[0];
int offHeight = height - 1;
return Math.round(offHeight - hue * offHeight);
}
@Override
public void paint(Graphics g)
{
// Paint the gradient
for (int y = 0; y < height; y++)
{
g.setColor(colorAt(y));
g.fillRect(0, y, PANEL_WIDTH, 1);
}
final int halfKnob = KNOB_HEIGHT / 2;
// Paint the selector
g.setColor(Color.WHITE);
g.fillRect(0, selectedY - 1, PANEL_WIDTH, KNOB_HEIGHT);
g.setColor(Color.BLACK);
g.drawLine(0, selectedY - halfKnob, PANEL_WIDTH, selectedY - halfKnob);
g.drawLine(0, selectedY + halfKnob, PANEL_WIDTH, selectedY + halfKnob);
}
/**
* Calculate hue color for current hue index.
*/
private Color colorAt(int y)
{
return Color.getHSBColor(1 - (float) y / (height - 1), 1, 1);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
import lombok.Getter;
class PreviewPanel extends JPanel
{
private static final int CHECKER_SIZE = 10;
@Getter
private Color color;
void setColor(Color c)
{
this.color = c;
this.paintImmediately(0, 0, this.getWidth(), this.getHeight());
}
@Override
public void paint(Graphics g)
{
super.paint(g);
for (int x = 0; x < getWidth(); x += CHECKER_SIZE)
{
for (int y = 0; y < getHeight(); y += CHECKER_SIZE)
{
int val = (x / CHECKER_SIZE + y / CHECKER_SIZE) % 2;
g.setColor(val == 0 ? Color.LIGHT_GRAY : Color.WHITE);
g.fillRect(x, y, CHECKER_SIZE, CHECKER_SIZE);
}
}
g.setColor(color);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
}

View File

@@ -0,0 +1,357 @@
/*
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui.components.colorpicker;
import com.google.common.base.Strings;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.function.Consumer;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import lombok.Getter;
import lombok.Setter;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.ColorUtil;
import org.pushingpixels.substance.internal.SubstanceSynapse;
public class RuneliteColorPicker extends JDialog
{
private final static int FRAME_WIDTH = 400;
private final static int FRAME_HEIGHT = 380;
private final static int TONE_PANEL_SIZE = 160;
private final static String BLANK_HEX = "#000";
private final ColorPanel colorPanel = new ColorPanel(TONE_PANEL_SIZE);
private final HuePanel huePanel = new HuePanel(TONE_PANEL_SIZE);
private final PreviewPanel afterPanel = new PreviewPanel();
private final ColorValuePanel redSlider = new ColorValuePanel("Red");
private final ColorValuePanel greenSlider = new ColorValuePanel("Green");
private final ColorValuePanel blueSlider = new ColorValuePanel("Blue");
private final ColorValuePanel alphaSlider = new ColorValuePanel("Opacity");
private final JTextField hexInput = new JTextField();
private final boolean alphaHidden;
@Getter
private Color selectedColor;
@Setter
private Consumer<Color> onColorChange;
public RuneliteColorPicker(Window parent, Color previousColor, String title, boolean alphaHidden)
{
super(parent, "RuneLite Color Picker - " + title, ModalityType.MODELESS);
this.selectedColor = previousColor;
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setResizable(false);
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setBackground(ColorScheme.PROGRESS_COMPLETE_COLOR);
JPanel content = new JPanel(new BorderLayout());
content.putClientProperty(SubstanceSynapse.COLORIZATION_FACTOR, 1.0);
content.setBorder(new EmptyBorder(15, 15, 15, 15));
JPanel colorSelection = new JPanel(new BorderLayout(15, 0));
JPanel leftPanel = new JPanel(new BorderLayout(15, 0));
leftPanel.add(huePanel, BorderLayout.WEST);
leftPanel.add(colorPanel, BorderLayout.CENTER);
JPanel rightPanel = new JPanel();
rightPanel.setLayout(new GridBagLayout());
GridBagConstraints cx = new GridBagConstraints();
cx.insets = new Insets(0, 0, 0, 0);
JLabel old = new JLabel("Previous");
old.setHorizontalAlignment(JLabel.CENTER);
JLabel next = new JLabel(" Current ");
next.setHorizontalAlignment(JLabel.CENTER);
PreviewPanel beforePanel = new PreviewPanel();
beforePanel.setColor(previousColor);
afterPanel.setColor(previousColor);
JPanel hexContainer = new JPanel(new GridBagLayout());
JLabel hexLabel = new JLabel("#");
hexInput.setBackground(ColorScheme.DARKER_GRAY_COLOR);
JLabel label = new JLabel("Hex color");
label.setVerticalAlignment(JLabel.BOTTOM);
cx.weightx = 0;
cx.fill = GridBagConstraints.BOTH;
cx.insets = new Insets(0, 1, 0, 1);
hexContainer.add(hexLabel, cx);
cx.weightx = 1;
cx.fill = GridBagConstraints.HORIZONTAL;
cx.gridwidth = GridBagConstraints.REMAINDER;
hexContainer.add(hexInput, cx);
cx.fill = GridBagConstraints.BOTH;
cx.gridwidth = GridBagConstraints.RELATIVE;
cx.weightx = 1;
cx.weighty = 1;
cx.gridy = 0;
cx.gridx = 0;
rightPanel.add(old, cx);
cx.gridx++;
rightPanel.add(next, cx);
cx.gridx = 0;
cx.gridy++;
cx.gridwidth = GridBagConstraints.RELATIVE;
cx.fill = GridBagConstraints.BOTH;
rightPanel.add(beforePanel, cx);
cx.gridx++;
rightPanel.add(afterPanel, cx);
cx.gridwidth = GridBagConstraints.REMAINDER;
cx.gridx = 0;
cx.gridy++;
rightPanel.add(label, cx);
cx.gridy++;
cx.fill = GridBagConstraints.HORIZONTAL;
rightPanel.add(hexContainer, cx);
JPanel slidersContainer = new JPanel(new GridLayout(4, 1, 0, 10));
slidersContainer.setBorder(new EmptyBorder(15, 0, 0, 0));
slidersContainer.add(redSlider);
slidersContainer.add(greenSlider);
slidersContainer.add(blueSlider);
slidersContainer.add(alphaSlider);
this.alphaHidden = alphaHidden;
if (alphaHidden)
{
alphaSlider.setVisible(false);
setSize(FRAME_WIDTH, FRAME_HEIGHT - 40);
}
colorSelection.add(leftPanel, BorderLayout.WEST);
colorSelection.add(rightPanel, BorderLayout.CENTER);
colorSelection.add(slidersContainer, BorderLayout.SOUTH);
content.add(colorSelection, BorderLayout.NORTH);
setContentPane(content);
// Reset selected color when clicking the old color
beforePanel.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (!alphaHidden)
{
alphaSlider.update(beforePanel.getColor().getAlpha());
}
colorChange(beforePanel.getColor());
updatePanels();
}
});
huePanel.setOnColorChange((y) ->
{
colorPanel.setBaseColor(y);
updateText();
});
colorPanel.setOnColorChange(this::colorChange);
((AbstractDocument) hexInput.getDocument()).setDocumentFilter(new DocumentFilter()
{
@Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String str, AttributeSet attrs)
throws BadLocationException
{
str = str.replaceAll("#|0x", "");
String text = RuneliteColorPicker.getReplacedText(fb, offset, length, str);
if (!ColorUtil.isHex(text))
{
Toolkit.getDefaultToolkit().beep();
return;
}
super.replace(fb, offset, length, str, attrs);
}
});
hexInput.addFocusListener(new FocusAdapter()
{
@Override
public void focusLost(FocusEvent e)
{
updateHex();
}
});
hexInput.addActionListener((ActionEvent e) -> updateHex());
redSlider.setOnValueChanged(i ->
{
colorChange(new Color(i, selectedColor.getGreen(), selectedColor.getBlue()));
updatePanels();
});
greenSlider.setOnValueChanged(i ->
{
colorChange(new Color(selectedColor.getRed(), i, selectedColor.getBlue()));
updatePanels();
});
blueSlider.setOnValueChanged(i ->
{
colorChange(new Color(selectedColor.getRed(), selectedColor.getGreen(), i));
updatePanels();
});
alphaSlider.setOnValueChanged(i ->
colorChange(new Color(selectedColor.getRed(), selectedColor.getGreen(), selectedColor.getBlue(), i)));
updatePanels();
updateText();
}
private void updatePanels()
{
huePanel.select(selectedColor);
colorPanel.moveToClosestColor(huePanel.getSelectedY(), selectedColor);
}
private void updateText()
{
String hex = alphaHidden ? ColorUtil.colorToHexCode(this.getSelectedColor()) : ColorUtil.colorToAlphaHexCode(this.getSelectedColor());
hexInput.setText(hex.toUpperCase());
afterPanel.setColor(selectedColor);
redSlider.update(this.selectedColor.getRed());
greenSlider.update(this.selectedColor.getGreen());
blueSlider.update(this.selectedColor.getBlue());
if (!alphaHidden)
{
alphaSlider.update(this.selectedColor.getAlpha());
}
}
private void colorChange(Color newColor)
{
if (newColor == this.selectedColor)
{
return;
}
this.selectedColor = newColor;
// Finally, before firing the color changed event, apply any transparency
// that might have been edited by the user
if (this.selectedColor.getAlpha() != this.alphaSlider.getValue())
{
this.selectedColor = new Color(
selectedColor.getRed(),
selectedColor.getGreen(),
selectedColor.getBlue(),
alphaSlider.getValue());
}
updateText();
if (onColorChange != null)
{
onColorChange.accept(this.selectedColor);
}
}
/**
* Parses the hex input, updating the color with the new values.
*/
private void updateHex()
{
String hex = hexInput.getText();
if (Strings.isNullOrEmpty(hex))
{
hex = BLANK_HEX;
}
Color color = ColorUtil.fromHex(hex);
if (color == null)
{
return;
}
if (!alphaHidden && ColorUtil.isAlphaHex(hex))
{
alphaSlider.update(color.getAlpha());
}
colorChange(color);
updatePanels();
}
/**
* Gets the whole string from the passed DocumentFilter replace.
*/
static String getReplacedText(DocumentFilter.FilterBypass fb, int offset, int length, String str)
throws BadLocationException
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder(doc.getText(0, doc.getLength()));
sb.replace(offset, offset + length, str);
return sb.toString();
}
}