text: /*
    ==============================================================================
  
     This file is part of the JUCE library.
     Copyright (c) 2017 - ROLI Ltd.
  
     JUCE is an open source library subject to commercial or open-source
     licensing.
  
     By using JUCE, you agree to the terms of both the JUCE 5 End-User License
     Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
     27th April 2017).
  
     End User License Agreement: www.juce.com/juce-5-licence
     Privacy Policy: www.juce.com/juce-5-privacy-policy
  
     Or: You may also use this code under the terms of the GPL v3 (see
     www.gnu.org/licenses).
  
     JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
     EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
     DISCLAIMED.
  
    ==============================================================================
  */
  
  namespace juce
  {
  
  struct ColourComponentSlider  : public Slider
  {
      ColourComponentSlider (const String& name)  : Slider (name)
      {
          setRange (0.0, 255.0, 1.0);
      }
  
      String getTextFromValue (double value) override
      {
          return String::toHexString ((int) value).toUpperCase().paddedLeft ('0', 2);
      }
  
      double getValueFromText (const String& text) override
      {
          return (double) text.getHexValue32();
      }
  };
  
  //==============================================================================
  class ColourSelector::ColourSpaceView  : public Component
  {
  public:
      ColourSpaceView (ColourSelector& cs, float& hue, float& sat, float& val, int edgeSize)
          : owner (cs), h (hue), s (sat), v (val), edge (edgeSize)
      {
          addAndMakeVisible (marker);
          setMouseCursor (MouseCursor::CrosshairCursor);
      }
  
      void paint (Graphics& g) override
      {
          if (colours.isNull())
          {
              auto width = getWidth() / 2;
              auto height = getHeight() / 2;
              colours = Image (Image::RGB, width, height, false);
  
              Image::BitmapData pixels (colours, Image::BitmapData::writeOnly);
  
              for (int y = 0; y < height; ++y)
              {
                  auto val = 1.0f - y / (float) height;
  
                  for (int x = 0; x < width; ++x)
                  {
                      auto sat = x / (float) width;
                      pixels.setPixelColour (x, y, Colour (h, sat, val, 1.0f));
                  }
              }
          }
  
          g.setOpacity (1.0f);
          g.drawImageTransformed (colours,
                                  RectanglePlacement (RectanglePlacement::stretchToFit)
                                      .getTransformToFit (colours.getBounds().toFloat(),
                                                          getLocalBounds().reduced (edge).toFloat()),
                                  false);
      }
  
      void mouseDown (const MouseEvent& e) override
      {
          mouseDrag (e);
      }
  
      void mouseDrag (const MouseEvent& e) override
      {
          auto sat =        (e.x - edge) / (float) (getWidth()  - edge * 2);
          auto val = 1.0f - (e.y - edge) / (float) (getHeight() - edge * 2);
  
          owner.setSV (sat, val);
      }
  
      void updateIfNeeded()
      {
          if (lastHue != h)
          {
              lastHue = h;
              colours = {};
              repaint();
          }
  
          updateMarker();
      }
  
      void resized() override
      {
          colours = {};
          updateMarker();
      }
  
  private:
      ColourSelector& owner;
      float& h;
      float& s;
      float& v;
      float lastHue = 0;
      const int edge;
      Image colours;
  
      struct ColourSpaceMarker  : public Component
      {
          ColourSpaceMarker()
          {
              setInterceptsMouseClicks (false, false);
          }
  
          void paint (Graphics& g) override
          {
              g.setColour (Colour::greyLevel (0.1f));
              g.drawEllipse (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 1.0f);
              g.setColour (Colour::greyLevel (0.9f));
              g.drawEllipse (2.0f, 2.0f, getWidth() - 4.0f, getHeight() - 4.0f, 1.0f);
          }
      };
  
      ColourSpaceMarker marker;
  
      void updateMarker()
      {
          auto markerSize = jmax (14, edge * 2);
          auto area = getLocalBounds().reduced (edge);
  
          marker.setBounds (Rectangle<int> (markerSize, markerSize)
                              .withCentre (area.getRelativePoint (s, 1.0f - v)));
      }
  
      JUCE_DECLARE_NON_COPYABLE (ColourSpaceView)
  };
  
  //==============================================================================
  class ColourSelector::HueSelectorComp  : public Component
  {
  public:
      HueSelectorComp (ColourSelector& cs, float& hue, int edgeSize)
          : owner (cs), h (hue), edge (edgeSize)
      {
          addAndMakeVisible (marker);
      }
  
      void paint (Graphics& g) override
      {
          ColourGradient cg;
          cg.isRadial = false;
          cg.point1.setXY (0.0f, (float) edge);
          cg.point2.setXY (0.0f, (float) getHeight());
  
          for (float i = 0.0f; i <= 1.0f; i += 0.02f)
              cg.addColour (i, Colour (i, 1.0f, 1.0f, 1.0f));
  
          g.setGradientFill (cg);
          g.fillRect (getLocalBounds().reduced (edge));
      }
  
      void resized() override
      {
          auto markerSize = jmax (14, edge * 2);
          auto area = getLocalBounds().reduced (edge);
  
          marker.setBounds (Rectangle<int> (getWidth(), markerSize)
                              .withCentre (area.getRelativePoint (0.5f, h)));
      }
  
      void mouseDown (const MouseEvent& e) override
      {
          mouseDrag (e);
      }
  
      void mouseDrag (const MouseEvent& e) override
      {
          owner.setHue ((e.y - edge) / (float) (getHeight() - edge * 2));
      }
  
      void updateIfNeeded()
      {
          resized();
      }
  
  private:
      ColourSelector& owner;
      float& h;
      const int edge;
  
      struct HueSelectorMarker  : public Component
      {
          HueSelectorMarker()
          {
              setInterceptsMouseClicks (false, false);
          }
  
          void paint (Graphics& g) override
          {
              auto cw = (float) getWidth();
              auto ch = (float) getHeight();
  
              Path p;
              p.addTriangle (1.0f, 1.0f,
                             cw * 0.3f, ch * 0.5f,
                             1.0f, ch - 1.0f);
  
              p.addTriangle (cw - 1.0f, 1.0f,
                             cw * 0.7f, ch * 0.5f,
                             cw - 1.0f, ch - 1.0f);
  
              g.setColour (Colours::white.withAlpha (0.75f));
              g.fillPath (p);
  
              g.setColour (Colours::black.withAlpha (0.75f));
              g.strokePath (p, PathStrokeType (1.2f));
          }
      };
  
      HueSelectorMarker marker;
  
      JUCE_DECLARE_NON_COPYABLE (HueSelectorComp)
  };
  
  //==============================================================================
  class ColourSelector::SwatchComponent   : public Component
  {
  public:
      SwatchComponent (ColourSelector& cs, int itemIndex)
          : owner (cs), index (itemIndex)
      {
      }
  
      void paint (Graphics& g) override
      {
          auto col = owner.getSwatchColour (index);
  
          g.fillCheckerBoard (getLocalBounds().toFloat(), 6.0f, 6.0f,
                              Colour (0xffdddddd).overlaidWith (col),
                              Colour (0xffffffff).overlaidWith (col));
      }
  
      void mouseDown (const MouseEvent&) override
      {
          PopupMenu m;
          m.addItem (1, TRANS("Use this swatch as the current colour"));
          m.addSeparator();
          m.addItem (2, TRANS("Set this swatch to the current colour"));
  
          m.showMenuAsync (PopupMenu::Options().withTargetComponent (this),
                           ModalCallbackFunction::forComponent (menuStaticCallback, this));
      }
  
  private:
      ColourSelector& owner;
      const int index;
  
      static void menuStaticCallback (int result, SwatchComponent* comp)
      {
          if (comp != nullptr)
          {
              if (result == 1)  comp->setColourFromSwatch();
              if (result == 2)  comp->setSwatchFromColour();
          }
      }
  
      void setColourFromSwatch()
      {
          owner.setCurrentColour (owner.getSwatchColour (index));
      }
  
      void setSwatchFromColour()
      {
          if (owner.getSwatchColour (index) != owner.getCurrentColour())
          {
              owner.setSwatchColour (index, owner.getCurrentColour());
              repaint();
          }
      }
  
      JUCE_DECLARE_NON_COPYABLE (SwatchComponent)
  };
  
  //==============================================================================
  ColourSelector::ColourSelector (int sectionsToShow, int edge, int gapAroundColourSpaceComponent)
      : colour (Colours::white),
        flags (sectionsToShow),
        edgeGap (edge)
  {
      // not much point having a selector with no components in it!
      jassert ((flags & (showColourAtTop | showSliders | showColourspace)) != 0);
  
      updateHSV();
  
      if ((flags & showSliders) != 0)
      {
          sliders[0].reset (new ColourComponentSlider (TRANS ("red")));
          sliders[1].reset (new ColourComponentSlider (TRANS ("green")));
          sliders[2].reset (new ColourComponentSlider (TRANS ("blue")));
          sliders[3].reset (new ColourComponentSlider (TRANS ("alpha")));
  
          addAndMakeVisible (sliders[0].get());
          addAndMakeVisible (sliders[1].get());
          addAndMakeVisible (sliders[2].get());
          addChildComponent (sliders[3].get());
  
          sliders[3]->setVisible ((flags & showAlphaChannel) != 0);
  
          // VS2015 needs some scoping braces around this if statement to
          // avoid a compiler bug.
          for (auto& slider : sliders)
          {
              slider->onValueChange = [this] { changeColour(); };
          }
      }
  
      if ((flags & showColourspace) != 0)
      {
          colourSpace.reset (new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent));
          hueSelector.reset (new HueSelectorComp (*this, h, gapAroundColourSpaceComponent));
  
          addAndMakeVisible (colourSpace.get());
          addAndMakeVisible (hueSelector.get());
      }
  
      update (dontSendNotification);
  }
  
  ColourSelector::~ColourSelector()
  {
      dispatchPendingMessages();
      swatchComponents.clear();
  }
  
  //==============================================================================
  Colour ColourSelector::getCurrentColour() const
  {
      return ((flags & showAlphaChannel) != 0) ? colour : colour.withAlpha ((uint8) 0xff);
  }
  
  void ColourSelector::setCurrentColour (Colour c, NotificationType notification)
  {
      if (c != colour)
      {
          colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff);
  
          updateHSV();
          update (notification);
      }
  }
  
  void ColourSelector::setHue (float newH)
  {
      newH = jlimit (0.0f, 1.0f, newH);
  
      if (h != newH)
      {
          h = newH;
          colour = Colour (h, s, v, colour.getFloatAlpha());
          update (sendNotification);
      }
  }
  
  void ColourSelector::setSV (float newS, float newV)
  {
      newS = jlimit (0.0f, 1.0f, newS);
      newV = jlimit (0.0f, 1.0f, newV);
  
      if (s != newS || v != newV)
      {
          s = newS;
          v = newV;
          colour = Colour (h, s, v, colour.getFloatAlpha());
          update (sendNotification);
      }
  }
  
  //==============================================================================
  void ColourSelector::updateHSV()
  {
      colour.getHSB (h, s, v);
  }
  
  void ColourSelector::update (NotificationType notification)
  {
      if (sliders[0] != nullptr)
      {
          sliders[0]->setValue ((int) colour.getRed(),   notification);
          sliders[1]->setValue ((int) colour.getGreen(), notification);
          sliders[2]->setValue ((int) colour.getBlue(),  notification);
          sliders[3]->setValue ((int) colour.getAlpha(), notification);
      }
  
      if (colourSpace != nullptr)
      {
          colourSpace->updateIfNeeded();
          hueSelector->updateIfNeeded();
      }
  
      if ((flags & showColourAtTop) != 0)
          repaint (previewArea);
  
      if (notification != dontSendNotification)
          sendChangeMessage();
  
      if (notification == sendNotificationSync)
          dispatchPendingMessages();
  }
  
  //==============================================================================
  void ColourSelector::paint (Graphics& g)
  {
      g.fillAll (findColour (backgroundColourId));
  
      if ((flags & showColourAtTop) != 0)
      {
          auto currentColour = getCurrentColour();
  
          g.fillCheckerBoard (previewArea.toFloat(), 10.0f, 10.0f,
                              Colour (0xffdddddd).overlaidWith (currentColour),
                              Colour (0xffffffff).overlaidWith (currentColour));
  
          g.setColour (Colours::white.overlaidWith (currentColour).contrasting());
          g.setFont (Font (14.0f, Font::bold));
          g.drawText (currentColour.toDisplayString ((flags & showAlphaChannel) != 0),
                      previewArea, Justification::centred, false);
      }
  
      if ((flags & showSliders) != 0)
      {
          g.setColour (findColour (labelTextColourId));
          g.setFont (11.0f);
  
          for (auto& slider : sliders)
          {
              if (slider->isVisible())
                  g.drawText (slider->getName() + ":",
                              0, slider->getY(),
                              slider->getX() - 8, slider->getHeight(),
                              Justification::centredRight, false);
          }
      }
  }
  
  void ColourSelector::resized()
  {
      const int swatchesPerRow = 8;
      const int swatchHeight = 22;
  
      const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3;
      const int numSwatches = getNumSwatches();
  
      const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0;
      const int sliderSpace = ((flags & showSliders) != 0)  ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0;
      const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap;
  
      previewArea.setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2);
  
      int y = topSpace;
  
      if ((flags & showColourspace) != 0)
      {
          const int hueWidth = jmin (50, proportionOfWidth (0.15f));
  
          colourSpace->setBounds (edgeGap, y,
                                  getWidth() - hueWidth - edgeGap - 4,
                                  getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap);
  
          hueSelector->setBounds (colourSpace->getRight() + 4, y,
                                  getWidth() - edgeGap - (colourSpace->getRight() + 4),
                                  colourSpace->getHeight());
  
          y = getHeight() - sliderSpace - swatchSpace - edgeGap;
      }
  
      if ((flags & showSliders) != 0)
      {
          auto sliderHeight = jmax (4, sliderSpace / numSliders);
  
          for (int i = 0; i < numSliders; ++i)
          {
              sliders[i]->setBounds (proportionOfWidth (0.2f), y,
                                     proportionOfWidth (0.72f), sliderHeight - 2);
  
              y += sliderHeight;
          }
      }
  
      if (numSwatches > 0)
      {
          const int startX = 8;
          const int xGap = 4;
          const int yGap = 4;
          const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow;
          y += edgeGap;
  
          if (swatchComponents.size() != numSwatches)
          {
              swatchComponents.clear();
  
              for (int i = 0; i < numSwatches; ++i)
              {
                  auto* sc = new SwatchComponent (*this, i);
                  swatchComponents.add (sc);
                  addAndMakeVisible (sc);
              }
          }
  
          int x = startX;
  
          for (int i = 0; i < swatchComponents.size(); ++i)
          {
              auto* sc = swatchComponents.getUnchecked(i);
  
              sc->setBounds (x + xGap / 2,
                             y + yGap / 2,
                             swatchWidth - xGap,
                             swatchHeight - yGap);
  
              if (((i + 1) % swatchesPerRow) == 0)
              {
                  x = startX;
                  y += swatchHeight;
              }
              else
              {
                  x += swatchWidth;
              }
          }
      }
  }
  
  void ColourSelector::changeColour()
  {
      if (sliders[0] != nullptr)
          setCurrentColour (Colour ((uint8) sliders[0]->getValue(),
                                    (uint8) sliders[1]->getValue(),
                                    (uint8) sliders[2]->getValue(),
                                    (uint8) sliders[3]->getValue()));
  }
  
  //==============================================================================
  int ColourSelector::getNumSwatches() const
  {
      return 0;
  }
  
  Colour ColourSelector::getSwatchColour (int) const
  {
      jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
      return Colours::black;
  }
  
  void ColourSelector::setSwatchColour (int, const Colour&)
  {
      jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
  }
  
  } // namespace juce
  