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="selector" type="app::ColorBar::ColorSelector" default="app::ColorBar::ColorSelector::SPECTRUM" /> | ||||
|       <option id="discrete_wheel" type="bool" default="false" /> | ||||
|       <option id="harmony" type="int" default="0" /> | ||||
|     </section> | ||||
|     <section id="tool_box"> | ||||
|       <option id="active_tool" type="std::string" default=""pencil"" /> | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ | |||
| #include "app/ui/status_bar.h" | ||||
| #include "base/bind.h" | ||||
| #include "base/pi.h" | ||||
| #include "base/scoped_value.h" | ||||
| #include "she/surface.h" | ||||
| #include "ui/graphics.h" | ||||
| #include "ui/menu.h" | ||||
|  | @ -33,10 +34,28 @@ using namespace app::skin; | |||
| using namespace gfx; | ||||
| 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() | ||||
|   : Widget(kGenericWidget) | ||||
|   , m_discrete(Preferences::instance().colorBar.discreteWheel()) | ||||
|   , m_harmony((Harmony)Preferences::instance().colorBar.harmony()) | ||||
|   , m_options("", kButtonWidget, kButtonWidget, kCheckWidget) | ||||
|   , m_harmonyPicked(false) | ||||
|   , m_lockColor(false) | ||||
| { | ||||
|   SkinTheme* theme = SkinTheme::instance(); | ||||
| 
 | ||||
|  | @ -59,10 +78,13 @@ ColorWheel::~ColorWheel() | |||
| 
 | ||||
| app::Color ColorWheel::pickColor(const gfx::Point& pos) const | ||||
| { | ||||
|   m_harmonyPicked = false; | ||||
| 
 | ||||
|   int u = (pos.x - (m_wheelBounds.x+m_wheelBounds.w/2)); | ||||
|   int v = (pos.y - (m_wheelBounds.y+m_wheelBounds.h/2)); | ||||
|   double d = std::sqrt(u*u + v*v); | ||||
| 
 | ||||
|   // Pick from the wheel
 | ||||
|   if (d < m_wheelRadius+2*guiscale()) { | ||||
|     double a = std::atan2(-v, u); | ||||
| 
 | ||||
|  | @ -92,13 +114,35 @@ app::Color ColorWheel::pickColor(const gfx::Point& pos) const | |||
|       MID(0, sat, 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) | ||||
| { | ||||
|   if (m_lockColor) | ||||
|     return; | ||||
| 
 | ||||
|   m_mainColor = color; | ||||
|   invalidate(); | ||||
| } | ||||
|  | @ -111,6 +155,31 @@ void ColorWheel::setDiscrete(bool state) | |||
|   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) | ||||
| { | ||||
|   ev.setPreferredSize(gfx::Size(32*ui::guiscale(), 32*ui::guiscale())); | ||||
|  | @ -166,17 +235,30 @@ void ColorWheel::onPaint(ui::PaintEvent& ev) | |||
|   } | ||||
| 
 | ||||
|   if (m_mainColor.getAlpha() > 0) { | ||||
|     int hue = m_mainColor.getHue()-30; | ||||
|     int sat = m_mainColor.getSaturation(); | ||||
|     gfx::Point pos = | ||||
|       m_wheelBounds.getCenter() + | ||||
|       gfx::Point(int(+std::cos(PI*hue/180)*double(m_wheelRadius)*sat/100.0), | ||||
|                  int(-std::sin(PI*hue/180)*double(m_wheelRadius)*sat/100.0)); | ||||
|     int n = getHarmonies(); | ||||
|     int boxsize = MIN(rc.w/10, rc.h/10); | ||||
| 
 | ||||
|     she::Surface* icon = theme->parts.colorWheelIndicator()->getBitmap(0); | ||||
|     g->drawRgbaSurface(icon, | ||||
|                        pos.x-icon->width()/2, | ||||
|                        pos.y-icon->height()/2); | ||||
|     for (int i=0; i<n; ++i) { | ||||
|       app::Color color = getColorInHarmony(i); | ||||
|       int hue = color.getHue()-30; | ||||
|       int sat = color.getSaturation(); | ||||
|       gfx::Point pos = | ||||
|         m_wheelBounds.getCenter() + | ||||
|         gfx::Point(int(+std::cos(PI*hue/180)*double(m_wheelRadius)*sat/100.0), | ||||
|                    int(-std::sin(PI*hue/180)*double(m_wheelRadius)*sat/100.0)); | ||||
| 
 | ||||
|       she::Surface* icon = theme->parts.colorWheelIndicator()->getBitmap(0); | ||||
|       g->drawRgbaSurface(icon, | ||||
|                          pos.x-icon->width()/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()); | ||||
| 
 | ||||
|       if (color != app::Color::fromMask()) { | ||||
|         base::ScopedValue<bool> switcher(m_lockColor, m_harmonyPicked, false); | ||||
| 
 | ||||
|         StatusBar::instance()->showColor(0, "", color); | ||||
|         if (hasCapture()) | ||||
|           ColorChange(color, mouseMsg->buttons()); | ||||
|  | @ -231,15 +315,46 @@ void ColorWheel::onOptions() | |||
| { | ||||
|   Menu menu; | ||||
|   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(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()) | ||||
|     discrete.setSelected(true); | ||||
|   if (isDiscrete()) 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( | ||||
|     [this]() { | ||||
|       setDiscrete(!isDiscrete()); | ||||
|     }); | ||||
|   discrete.Click.connect(Bind<void>(&ColorWheel::setDiscrete, this, !isDiscrete())); | ||||
|   none.Click.connect(Bind<void>(&ColorWheel::setHarmony, this, Harmony::NONE)); | ||||
|   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(); | ||||
|   menu.showPopup(gfx::Point(rc.x+rc.w, rc.y)); | ||||
|  |  | |||
|  | @ -19,6 +19,18 @@ namespace app { | |||
| 
 | ||||
|   class ColorWheel : public ui::Widget { | ||||
|   public: | ||||
|     enum class Harmony { | ||||
|       NONE, | ||||
|       COMPLEMENTARY, | ||||
|       MONOCHROMATIC, | ||||
|       ANALOGOUS, | ||||
|       SPLIT, | ||||
|       TRIADIC, | ||||
|       TETRADIC, | ||||
|       SQUARE, | ||||
|       LAST = SQUARE | ||||
|     }; | ||||
| 
 | ||||
|     ColorWheel(); | ||||
|     ~ColorWheel(); | ||||
| 
 | ||||
|  | @ -28,6 +40,8 @@ namespace app { | |||
|     bool isDiscrete() const { return m_discrete; } | ||||
|     void setDiscrete(bool state); | ||||
| 
 | ||||
|     void setHarmony(Harmony harmony); | ||||
| 
 | ||||
|     // Signals
 | ||||
|     Signal2<void, const app::Color&, ui::MouseButtons> ColorChange; | ||||
| 
 | ||||
|  | @ -37,13 +51,25 @@ namespace app { | |||
|     void onPaint(ui::PaintEvent& ev) override; | ||||
|     bool onProcessMessage(ui::Message* msg) override; | ||||
|     void onOptions(); | ||||
|     int getHarmonies() const; | ||||
|     app::Color getColorInHarmony(int i) const; | ||||
| 
 | ||||
|     gfx::Rect m_clientBounds; | ||||
|     gfx::Rect m_wheelBounds; | ||||
|     int m_wheelRadius; | ||||
|     bool m_discrete; | ||||
|     Harmony m_harmony; | ||||
|     ui::ButtonBase m_options; | ||||
|     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
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue