/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #ifndef JUCER_PAINTELEMENTIMAGE_H_INCLUDED #define JUCER_PAINTELEMENTIMAGE_H_INCLUDED #include "../jucer_PaintRoutine.h" #include "../properties/jucer_FilePropertyComponent.h" #include "jucer_ImageResourceProperty.h" #include "jucer_PaintElementUndoableAction.h" //============================================================================== class PaintElementImage : public PaintElement { public: PaintElementImage (PaintRoutine* pr) : PaintElement (pr, "Image"), opacity (1.0), mode (stretched) { } enum StretchMode { stretched = 0, proportional = 1, proportionalReducingOnly = 2 }; const Drawable* getDrawable() { if (JucerDocument* const document = getDocument()) return document->getResources().getDrawable (resourceName); return nullptr; } void draw (Graphics& g, const ComponentLayout* layout, const Rectangle& parentArea) { const Rectangle r (position.getRectangle (parentArea, layout)); if (const Drawable* const image = getDrawable()) { image->drawWithin (g, r.toFloat(), mode == stretched ? RectanglePlacement::stretchToFit : (mode == proportionalReducingOnly ? (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize) : RectanglePlacement::centred), (float) opacity); } else { g.setColour (Colours::grey.withAlpha (0.5f)); g.fillRect (r); g.setColour (Colours::black); g.drawText ("(image missing)", r.getX(), r.getY(), r.getWidth(), r.getHeight(), Justification::centred, true); } } //============================================================================== void getEditableProperties (Array & props) { PaintElement::getEditableProperties (props); props.add (new ImageElementResourceProperty (this)); props.add (new StretchModeProperty (this)); props.add (new OpacityProperty (this)); props.add (new ResetSizeProperty (this)); } void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) { String r; if (opacity > 0) { if (dynamic_cast (getDrawable()) != 0) { const String imageVariable ("cachedImage_" + resourceName.replace ("::", "_") + "_" + String (code.getUniqueSuffix())); code.addImageResourceLoader (imageVariable, resourceName); if (opacity >= 254.0 / 255.0) r << "g.setColour (Colours::black);\n"; else r << "g.setColour (Colours::black.withAlpha (" << CodeHelpers::floatLiteral (opacity, 3) << "));\n"; String x, y, w, h; positionToCode (position, getDocument()->getComponentLayout(), x, y, w, h); if (mode == stretched) { r << "g.drawImage (" << imageVariable << ",\n " << x << ", " << y << ", " << w << ", " << h << ",\n 0, 0, " << imageVariable << ".getWidth(), " << imageVariable << ".getHeight());\n\n"; } else { r << "g.drawImageWithin (" << imageVariable << ",\n " << x << ", " << y << ", " << w << ", " << h << ",\n "; if (mode == proportionalReducingOnly) r << "RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize"; else r << "RectanglePlacement::centred"; r << ",\n false);\n\n"; } paintMethodCode += r; } else { if (resourceName.isNotEmpty()) { const String imageVariable ("drawable" + String (code.getUniqueSuffix())); code.privateMemberDeclarations << "ScopedPointer " << imageVariable << ";\n"; code.constructorCode << imageVariable << " = Drawable::createFromImageData (" << resourceName << ", " << resourceName << "Size);\n"; code.destructorCode << imageVariable << " = nullptr;\n"; if (opacity >= 254.0 / 255.0) r << "g.setColour (Colours::black);\n"; else r << "g.setColour (Colours::black.withAlpha (" << CodeHelpers::floatLiteral (opacity, 3) << "));\n"; String x, y, w, h; positionToCode (position, code.document->getComponentLayout(), x, y, w, h); r << "jassert (" << imageVariable << " != 0);\n" << "if (" << imageVariable << " != 0)\n " << imageVariable << "->drawWithin (g, Rectangle (" << x << ", " << y << ", " << w << ", " << h << "),\n" << String::repeatedString (" ", imageVariable.length() + 18) << (mode == stretched ? "RectanglePlacement::stretchToFit" : (mode == proportionalReducingOnly ? "RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize" : "RectanglePlacement::centred")) << ", " << CodeHelpers::floatLiteral (opacity, 3) << ");\n\n"; paintMethodCode += r; } } } } //============================================================================== class SetResourceAction : public PaintElementUndoableAction { public: SetResourceAction (PaintElementImage* const element, const String& newResource_) : PaintElementUndoableAction (element), newResource (newResource_) { oldResource = element->getResource(); } bool perform() { showCorrectTab(); getElement()->setResource (newResource, false); return true; } bool undo() { showCorrectTab(); getElement()->setResource (oldResource, false); return true; } private: String newResource, oldResource; }; void setResource (const String& newName, const bool undoable) { if (resourceName != newName) { if (undoable) { perform (new SetResourceAction (this, newName), "Change image resource"); } else { resourceName = newName; changed(); } } repaint(); } String getResource() const { return resourceName; } //============================================================================== class SetOpacityAction : public PaintElementUndoableAction { public: SetOpacityAction (PaintElementImage* const element, const double newOpacity_) : PaintElementUndoableAction (element), newOpacity (newOpacity_) { oldOpacity = element->getOpacity(); } bool perform() { showCorrectTab(); getElement()->setOpacity (newOpacity, false); return true; } bool undo() { showCorrectTab(); getElement()->setOpacity (oldOpacity, false); return true; } private: double newOpacity, oldOpacity; }; void setOpacity (double newOpacity, const bool undoable) { newOpacity = jlimit (0.0, 1.0, newOpacity); if (opacity != newOpacity) { if (undoable) { perform (new SetOpacityAction (this, newOpacity), "Change image opacity"); } else { opacity = newOpacity; changed(); } } } double getOpacity() const noexcept { return opacity; } //============================================================================== static const char* getTagName() noexcept { return "IMAGE"; } void resetToImageSize() { if (const Drawable* const image = getDrawable()) { if (PaintRoutineEditor* ed = dynamic_cast (getParentComponent())) { const Rectangle parentArea (ed->getComponentArea()); Rectangle r (getCurrentBounds (parentArea)); Rectangle b (image->getDrawableBounds()); r.setSize ((int) (b.getWidth() + 0.999f), (int) (b.getHeight() + 0.999f)); setCurrentBounds (r, parentArea, true); } } } //============================================================================== class SetStretchModeAction : public PaintElementUndoableAction { public: SetStretchModeAction (PaintElementImage* const element, const StretchMode newValue_) : PaintElementUndoableAction (element), newValue (newValue_) { oldValue = element->getStretchMode(); } bool perform() { showCorrectTab(); getElement()->setStretchMode (newValue, false); return true; } bool undo() { showCorrectTab(); getElement()->setStretchMode (oldValue, false); return true; } private: StretchMode newValue, oldValue; }; StretchMode getStretchMode() const noexcept { return mode; } void setStretchMode (const StretchMode newMode, const bool undoable) { if (mode != newMode) { if (undoable) { perform (new SetStretchModeAction (this, newMode), "Change image mode"); } else { mode = newMode; changed(); } } } //============================================================================== XmlElement* createXml() const { XmlElement* e = new XmlElement (getTagName()); position.applyToXml (*e); e->setAttribute ("resource", resourceName); e->setAttribute ("opacity", opacity); e->setAttribute ("mode", (int) mode); return e; } bool loadFromXml (const XmlElement& xml) { if (xml.hasTagName (getTagName())) { position.restoreFromXml (xml, position); resourceName = xml.getStringAttribute ("resource", String()); opacity = xml.getDoubleAttribute ("opacity", 1.0); mode = (StretchMode) xml.getIntAttribute ("mode", (int) stretched); repaint(); return true; } jassertfalse; return false; } private: String resourceName; double opacity; StretchMode mode; //============================================================================== class ImageElementResourceProperty : public ImageResourceProperty { public: ImageElementResourceProperty (PaintElementImage* const e) : ImageResourceProperty (e, "image source") { } void setResource (const String& newName) { if (element != nullptr) element->setResource (newName, true); } String getResource() const { if (element != nullptr) return element->getResource(); return String(); } }; //============================================================================== class OpacityProperty : public SliderPropertyComponent { public: OpacityProperty (PaintElementImage* const e) : SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001), listener (e) { listener.setPropertyToRefresh (*this); } void setValue (double newValue) { listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly(); listener.owner->setOpacity (newValue, true); } double getValue() const { return listener.owner->getOpacity(); } ElementListener listener; }; class StretchModeProperty : public ChoicePropertyComponent { public: StretchModeProperty (PaintElementImage* const e) : ChoicePropertyComponent ("stretch mode"), listener (e) { listener.setPropertyToRefresh (*this); choices.add ("Stretched to fit"); choices.add ("Maintain aspect ratio"); choices.add ("Maintain aspect ratio, only reduce in size"); } void setIndex (int newIndex) { listener.owner->setStretchMode ((StretchMode) newIndex, true); } int getIndex() const { return (int) listener.owner->getStretchMode(); } ElementListener listener; }; class ResetSizeProperty : public ButtonPropertyComponent { public: ResetSizeProperty (PaintElementImage* const e) : ButtonPropertyComponent ("reset", false), element (e) { } void buttonClicked() { element->resetToImageSize(); } String getButtonText() const { return "reset to image size"; } private: PaintElementImage* const element; }; }; #endif // JUCER_PAINTELEMENTIMAGE_H_INCLUDED