mirror of https://github.com/aseprite/aseprite.git
Add color harmonies in the ColorWheel widget (issue #707)
This commit is contained in:
parent
b0877df0cb
commit
4b064333cc
|
|
@ -105,6 +105,7 @@
|
||||||
<option id="bg_color" type="app::Color" default="app::Color::fromRgb(0, 0, 0)" />
|
<option id="bg_color" type="app::Color" default="app::Color::fromRgb(0, 0, 0)" />
|
||||||
<option id="selector" type="app::ColorBar::ColorSelector" default="app::ColorBar::ColorSelector::SPECTRUM" />
|
<option id="selector" type="app::ColorBar::ColorSelector" default="app::ColorBar::ColorSelector::SPECTRUM" />
|
||||||
<option id="discrete_wheel" type="bool" default="false" />
|
<option id="discrete_wheel" type="bool" default="false" />
|
||||||
|
<option id="harmony" type="int" default="0" />
|
||||||
</section>
|
</section>
|
||||||
<section id="tool_box">
|
<section id="tool_box">
|
||||||
<option id="active_tool" type="std::string" default=""pencil"" />
|
<option id="active_tool" type="std::string" default=""pencil"" />
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#include "app/ui/status_bar.h"
|
#include "app/ui/status_bar.h"
|
||||||
#include "base/bind.h"
|
#include "base/bind.h"
|
||||||
#include "base/pi.h"
|
#include "base/pi.h"
|
||||||
|
#include "base/scoped_value.h"
|
||||||
#include "she/surface.h"
|
#include "she/surface.h"
|
||||||
#include "ui/graphics.h"
|
#include "ui/graphics.h"
|
||||||
#include "ui/menu.h"
|
#include "ui/menu.h"
|
||||||
|
|
@ -33,10 +34,28 @@ using namespace app::skin;
|
||||||
using namespace gfx;
|
using namespace gfx;
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int n;
|
||||||
|
int hues[4];
|
||||||
|
int sats[4];
|
||||||
|
} harmonies[] = {
|
||||||
|
{ 1, { 0, 0, 0, 0 }, { 100, 0, 0, 0 } }, // NONE
|
||||||
|
{ 2, { 0, 180, 0, 0 }, { 100, 100, 0, 0 } }, // COMPLEMENTARY
|
||||||
|
{ 2, { 0, 0, 0, 0 }, { 100, 50, 0, 0 } }, // MONOCHROMATIC
|
||||||
|
{ 3, { 0, 30, 330, 0 }, { 100, 100, 100, 0 } }, // ANALOGOUS
|
||||||
|
{ 3, { 0, 150, 210, 0 }, { 100, 100, 100, 0 } }, // SPLIT
|
||||||
|
{ 3, { 0, 120, 240, 0 }, { 100, 100, 100, 0 } }, // TRIADIC
|
||||||
|
{ 4, { 0, 120, 180, 300 }, { 100, 100, 100, 100 } }, // TETRADIC
|
||||||
|
{ 4, { 0, 90, 180, 270 }, { 100, 100, 100, 100 } }, // SQUARE
|
||||||
|
};
|
||||||
|
|
||||||
ColorWheel::ColorWheel()
|
ColorWheel::ColorWheel()
|
||||||
: Widget(kGenericWidget)
|
: Widget(kGenericWidget)
|
||||||
, m_discrete(Preferences::instance().colorBar.discreteWheel())
|
, m_discrete(Preferences::instance().colorBar.discreteWheel())
|
||||||
|
, m_harmony((Harmony)Preferences::instance().colorBar.harmony())
|
||||||
, m_options("", kButtonWidget, kButtonWidget, kCheckWidget)
|
, m_options("", kButtonWidget, kButtonWidget, kCheckWidget)
|
||||||
|
, m_harmonyPicked(false)
|
||||||
|
, m_lockColor(false)
|
||||||
{
|
{
|
||||||
SkinTheme* theme = SkinTheme::instance();
|
SkinTheme* theme = SkinTheme::instance();
|
||||||
|
|
||||||
|
|
@ -59,10 +78,13 @@ ColorWheel::~ColorWheel()
|
||||||
|
|
||||||
app::Color ColorWheel::pickColor(const gfx::Point& pos) const
|
app::Color ColorWheel::pickColor(const gfx::Point& pos) const
|
||||||
{
|
{
|
||||||
|
m_harmonyPicked = false;
|
||||||
|
|
||||||
int u = (pos.x - (m_wheelBounds.x+m_wheelBounds.w/2));
|
int u = (pos.x - (m_wheelBounds.x+m_wheelBounds.w/2));
|
||||||
int v = (pos.y - (m_wheelBounds.y+m_wheelBounds.h/2));
|
int v = (pos.y - (m_wheelBounds.y+m_wheelBounds.h/2));
|
||||||
double d = std::sqrt(u*u + v*v);
|
double d = std::sqrt(u*u + v*v);
|
||||||
|
|
||||||
|
// Pick from the wheel
|
||||||
if (d < m_wheelRadius+2*guiscale()) {
|
if (d < m_wheelRadius+2*guiscale()) {
|
||||||
double a = std::atan2(-v, u);
|
double a = std::atan2(-v, u);
|
||||||
|
|
||||||
|
|
@ -92,13 +114,35 @@ app::Color ColorWheel::pickColor(const gfx::Point& pos) const
|
||||||
MID(0, sat, 100),
|
MID(0, sat, 100),
|
||||||
100);
|
100);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return app::Color::fromMask();
|
// Pick harmonies
|
||||||
|
if (m_mainColor.getAlpha() > 0) {
|
||||||
|
const gfx::Rect& rc = m_clientBounds;
|
||||||
|
int n = getHarmonies();
|
||||||
|
int boxsize = MIN(rc.w/10, rc.h/10);
|
||||||
|
|
||||||
|
for (int i=0; i<n; ++i) {
|
||||||
|
app::Color color = getColorInHarmony(i);
|
||||||
|
int hue = color.getHue()-30;
|
||||||
|
int sat = color.getSaturation();
|
||||||
|
|
||||||
|
if (gfx::Rect(rc.x+rc.w-(n-i)*boxsize,
|
||||||
|
rc.y+rc.h-boxsize,
|
||||||
|
boxsize, boxsize).contains(pos)) {
|
||||||
|
m_harmonyPicked = true;
|
||||||
|
return color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app::Color::fromMask();
|
||||||
|
}
|
||||||
|
|
||||||
void ColorWheel::selectColor(const app::Color& color)
|
void ColorWheel::selectColor(const app::Color& color)
|
||||||
{
|
{
|
||||||
|
if (m_lockColor)
|
||||||
|
return;
|
||||||
|
|
||||||
m_mainColor = color;
|
m_mainColor = color;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +155,31 @@ void ColorWheel::setDiscrete(bool state)
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ColorWheel::setHarmony(Harmony harmony)
|
||||||
|
{
|
||||||
|
m_harmony = harmony;
|
||||||
|
Preferences::instance().colorBar.harmony((int)m_harmony);
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ColorWheel::getHarmonies() const
|
||||||
|
{
|
||||||
|
int i = MID(0, (int)m_harmony, (int)Harmony::LAST);
|
||||||
|
return harmonies[i].n;
|
||||||
|
}
|
||||||
|
|
||||||
|
app::Color ColorWheel::getColorInHarmony(int j) const
|
||||||
|
{
|
||||||
|
int i = MID(0, (int)m_harmony, (int)Harmony::LAST);
|
||||||
|
j = MID(0, j, harmonies[i].n-1);
|
||||||
|
int hue = m_mainColor.getHue() + harmonies[i].hues[j];
|
||||||
|
int sat = m_mainColor.getSaturation() * harmonies[i].sats[j] / 100;
|
||||||
|
return app::Color::fromHsv(hue % 360,
|
||||||
|
MID(0, sat, 100),
|
||||||
|
m_mainColor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
void ColorWheel::onPreferredSize(PreferredSizeEvent& ev)
|
void ColorWheel::onPreferredSize(PreferredSizeEvent& ev)
|
||||||
{
|
{
|
||||||
ev.setPreferredSize(gfx::Size(32*ui::guiscale(), 32*ui::guiscale()));
|
ev.setPreferredSize(gfx::Size(32*ui::guiscale(), 32*ui::guiscale()));
|
||||||
|
|
@ -166,8 +235,13 @@ void ColorWheel::onPaint(ui::PaintEvent& ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_mainColor.getAlpha() > 0) {
|
if (m_mainColor.getAlpha() > 0) {
|
||||||
int hue = m_mainColor.getHue()-30;
|
int n = getHarmonies();
|
||||||
int sat = m_mainColor.getSaturation();
|
int boxsize = MIN(rc.w/10, rc.h/10);
|
||||||
|
|
||||||
|
for (int i=0; i<n; ++i) {
|
||||||
|
app::Color color = getColorInHarmony(i);
|
||||||
|
int hue = color.getHue()-30;
|
||||||
|
int sat = color.getSaturation();
|
||||||
gfx::Point pos =
|
gfx::Point pos =
|
||||||
m_wheelBounds.getCenter() +
|
m_wheelBounds.getCenter() +
|
||||||
gfx::Point(int(+std::cos(PI*hue/180)*double(m_wheelRadius)*sat/100.0),
|
gfx::Point(int(+std::cos(PI*hue/180)*double(m_wheelRadius)*sat/100.0),
|
||||||
|
|
@ -177,6 +251,14 @@ void ColorWheel::onPaint(ui::PaintEvent& ev)
|
||||||
g->drawRgbaSurface(icon,
|
g->drawRgbaSurface(icon,
|
||||||
pos.x-icon->width()/2,
|
pos.x-icon->width()/2,
|
||||||
pos.y-icon->height()/2);
|
pos.y-icon->height()/2);
|
||||||
|
|
||||||
|
g->fillRect(gfx::rgba(color.getRed(),
|
||||||
|
color.getGreen(),
|
||||||
|
color.getBlue(), 255),
|
||||||
|
gfx::Rect(rc.x+rc.w-(n-i)*boxsize,
|
||||||
|
rc.y+rc.h-boxsize,
|
||||||
|
boxsize, boxsize));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,6 +278,8 @@ bool ColorWheel::onProcessMessage(ui::Message* msg)
|
||||||
- getBounds().getOrigin());
|
- getBounds().getOrigin());
|
||||||
|
|
||||||
if (color != app::Color::fromMask()) {
|
if (color != app::Color::fromMask()) {
|
||||||
|
base::ScopedValue<bool> switcher(m_lockColor, m_harmonyPicked, false);
|
||||||
|
|
||||||
StatusBar::instance()->showColor(0, "", color);
|
StatusBar::instance()->showColor(0, "", color);
|
||||||
if (hasCapture())
|
if (hasCapture())
|
||||||
ColorChange(color, mouseMsg->buttons());
|
ColorChange(color, mouseMsg->buttons());
|
||||||
|
|
@ -231,15 +315,46 @@ void ColorWheel::onOptions()
|
||||||
{
|
{
|
||||||
Menu menu;
|
Menu menu;
|
||||||
MenuItem discrete("Discrete");
|
MenuItem discrete("Discrete");
|
||||||
|
MenuItem none("Without Harmonies");
|
||||||
|
MenuItem complementary("Complementary");
|
||||||
|
MenuItem monochromatic("Monochromatic");
|
||||||
|
MenuItem analogous("Analogous");
|
||||||
|
MenuItem split("Split-Complementary");
|
||||||
|
MenuItem triadic("Triadic");
|
||||||
|
MenuItem tetradic("Tetradic");
|
||||||
|
MenuItem square("Square");
|
||||||
menu.addChild(&discrete);
|
menu.addChild(&discrete);
|
||||||
|
menu.addChild(new MenuSeparator);
|
||||||
|
menu.addChild(&none);
|
||||||
|
menu.addChild(&complementary);
|
||||||
|
menu.addChild(&monochromatic);
|
||||||
|
menu.addChild(&analogous);
|
||||||
|
menu.addChild(&split);
|
||||||
|
menu.addChild(&triadic);
|
||||||
|
menu.addChild(&tetradic);
|
||||||
|
menu.addChild(&square);
|
||||||
|
|
||||||
if (isDiscrete())
|
if (isDiscrete()) discrete.setSelected(true);
|
||||||
discrete.setSelected(true);
|
switch (m_harmony) {
|
||||||
|
case Harmony::NONE: none.setSelected(true); break;
|
||||||
|
case Harmony::COMPLEMENTARY: complementary.setSelected(true); break;
|
||||||
|
case Harmony::MONOCHROMATIC: monochromatic.setSelected(true); break;
|
||||||
|
case Harmony::ANALOGOUS: analogous.setSelected(true); break;
|
||||||
|
case Harmony::SPLIT: split.setSelected(true); break;
|
||||||
|
case Harmony::TRIADIC: triadic.setSelected(true); break;
|
||||||
|
case Harmony::TETRADIC: tetradic.setSelected(true); break;
|
||||||
|
case Harmony::SQUARE: square.setSelected(true); break;
|
||||||
|
}
|
||||||
|
|
||||||
discrete.Click.connect(
|
discrete.Click.connect(Bind<void>(&ColorWheel::setDiscrete, this, !isDiscrete()));
|
||||||
[this]() {
|
none.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::NONE));
|
||||||
setDiscrete(!isDiscrete());
|
complementary.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::COMPLEMENTARY));
|
||||||
});
|
monochromatic.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::MONOCHROMATIC));
|
||||||
|
analogous.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::ANALOGOUS));
|
||||||
|
split.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::SPLIT));
|
||||||
|
triadic.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::TRIADIC));
|
||||||
|
tetradic.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::TETRADIC));
|
||||||
|
square.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::SQUARE));
|
||||||
|
|
||||||
gfx::Rect rc = m_options.getBounds();
|
gfx::Rect rc = m_options.getBounds();
|
||||||
menu.showPopup(gfx::Point(rc.x+rc.w, rc.y));
|
menu.showPopup(gfx::Point(rc.x+rc.w, rc.y));
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,18 @@ namespace app {
|
||||||
|
|
||||||
class ColorWheel : public ui::Widget {
|
class ColorWheel : public ui::Widget {
|
||||||
public:
|
public:
|
||||||
|
enum class Harmony {
|
||||||
|
NONE,
|
||||||
|
COMPLEMENTARY,
|
||||||
|
MONOCHROMATIC,
|
||||||
|
ANALOGOUS,
|
||||||
|
SPLIT,
|
||||||
|
TRIADIC,
|
||||||
|
TETRADIC,
|
||||||
|
SQUARE,
|
||||||
|
LAST = SQUARE
|
||||||
|
};
|
||||||
|
|
||||||
ColorWheel();
|
ColorWheel();
|
||||||
~ColorWheel();
|
~ColorWheel();
|
||||||
|
|
||||||
|
|
@ -28,6 +40,8 @@ namespace app {
|
||||||
bool isDiscrete() const { return m_discrete; }
|
bool isDiscrete() const { return m_discrete; }
|
||||||
void setDiscrete(bool state);
|
void setDiscrete(bool state);
|
||||||
|
|
||||||
|
void setHarmony(Harmony harmony);
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
Signal2<void, const app::Color&, ui::MouseButtons> ColorChange;
|
Signal2<void, const app::Color&, ui::MouseButtons> ColorChange;
|
||||||
|
|
||||||
|
|
@ -37,13 +51,25 @@ namespace app {
|
||||||
void onPaint(ui::PaintEvent& ev) override;
|
void onPaint(ui::PaintEvent& ev) override;
|
||||||
bool onProcessMessage(ui::Message* msg) override;
|
bool onProcessMessage(ui::Message* msg) override;
|
||||||
void onOptions();
|
void onOptions();
|
||||||
|
int getHarmonies() const;
|
||||||
|
app::Color getColorInHarmony(int i) const;
|
||||||
|
|
||||||
gfx::Rect m_clientBounds;
|
gfx::Rect m_clientBounds;
|
||||||
gfx::Rect m_wheelBounds;
|
gfx::Rect m_wheelBounds;
|
||||||
int m_wheelRadius;
|
int m_wheelRadius;
|
||||||
bool m_discrete;
|
bool m_discrete;
|
||||||
|
Harmony m_harmony;
|
||||||
ui::ButtonBase m_options;
|
ui::ButtonBase m_options;
|
||||||
app::Color m_mainColor;
|
app::Color m_mainColor;
|
||||||
|
|
||||||
|
// Internal flag used to know if after pickColor() we selected an
|
||||||
|
// harmony.
|
||||||
|
mutable bool m_harmonyPicked;
|
||||||
|
|
||||||
|
// Internal flag used to lock the modification of m_mainColor.
|
||||||
|
// When the user picks a color harmony, we don't want to change
|
||||||
|
// the main color.
|
||||||
|
bool m_lockColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue