| @@ -4,6 +4,26 @@ JUCE breaking changes | |||
| develop | |||
| ======= | |||
| Change | |||
| ------ | |||
| ListBox::createSnapshotOfRows now returns ScaledImage instead of Image. | |||
| Possible Issues | |||
| --------------- | |||
| User code that overrides this function will fail to build. | |||
| Workaround | |||
| ---------- | |||
| To emulate the old behaviour, simply wrap the Image that was previous returned | |||
| into a ScaledImage and return that instead. | |||
| Rationale | |||
| --------- | |||
| Returning a ScaledImage allows the overriding function to specify the scale | |||
| at which the image should be drawn. Returning an oversampled image will provide | |||
| smoother-looking results on high resolution displays. | |||
| Change | |||
| ------ | |||
| AudioFrameRate::frameRate is now a class type instead of an enum. | |||
| @@ -0,0 +1,82 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2020 - Raw Material Software Limited | |||
| 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 6 End-User License | |||
| Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). | |||
| End User License Agreement: www.juce.com/juce-6-licence | |||
| Privacy Policy: www.juce.com/juce-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 | |||
| { | |||
| /** | |||
| An image that will be resampled before it is drawn. | |||
| A plain Image only stores plain pixels, but does not store any information | |||
| about how these pixels correspond to points. This means that if the image's | |||
| dimensions are interpreted as points, then the image will be blurry when | |||
| drawn on high resolution displays. If the image's dimensions are instead | |||
| interpreted as corresponding to exact pixel positions, then the logical | |||
| size of the image will change depending on the scale factor of the screen | |||
| used to draw it. | |||
| The ScaledImage class is designed to store an image alongside a scale | |||
| factor that informs a renderer how to convert between the image's pixels | |||
| and points. | |||
| */ | |||
| class JUCE_API ScaledImage | |||
| { | |||
| public: | |||
| /** Creates a ScaledImage with an invalid image and unity scale. | |||
| */ | |||
| ScaledImage() = default; | |||
| /** Creates a ScaledImage from an Image, where the dimensions of the image | |||
| in pixels are exactly equal to its dimensions in points. | |||
| */ | |||
| explicit ScaledImage (const Image& imageIn) | |||
| : ScaledImage (imageIn, 1.0) {} | |||
| /** Creates a ScaledImage from an Image, using a custom scale factor. | |||
| A scale of 1.0 means that the image's dimensions in pixels is equal to | |||
| its dimensions in points. | |||
| A scale of 2.0 means that the image contains 2 pixels per point in each | |||
| direction. | |||
| */ | |||
| ScaledImage (const Image& imageIn, double scaleIn) | |||
| : image (imageIn), scaleFactor (scaleIn) {} | |||
| /** Returns the image at its original dimentions. */ | |||
| Image getImage() const { return image; } | |||
| /** Returns the image's scale. */ | |||
| double getScale() const { return scaleFactor; } | |||
| /** Returns the bounds of this image expressed in points. */ | |||
| Rectangle<double> getScaledBounds() const { return image.getBounds().toDouble() / scaleFactor; } | |||
| private: | |||
| Image image; | |||
| double scaleFactor = 1.0; | |||
| }; | |||
| } // namespace juce | |||
| @@ -140,6 +140,7 @@ namespace juce | |||
| #include "contexts/juce_GraphicsContext.h" | |||
| #include "contexts/juce_LowLevelGraphicsContext.h" | |||
| #include "images/juce_Image.h" | |||
| #include "images/juce_ScaledImage.h" | |||
| #include "colour/juce_FillType.h" | |||
| #include "native/juce_RenderingHelpers.h" | |||
| #include "contexts/juce_LowLevelGraphicsSoftwareRenderer.h" | |||
| @@ -35,14 +35,15 @@ class DragAndDropContainer::DragImageComponent : public Component, | |||
| private Timer | |||
| { | |||
| public: | |||
| DragImageComponent (const Image& im, | |||
| DragImageComponent (const ScaledImage& im, | |||
| const var& desc, | |||
| Component* const sourceComponent, | |||
| const MouseInputSource* draggingSource, | |||
| DragAndDropContainer& ddc, | |||
| Point<int> offset) | |||
| : sourceDetails (desc, sourceComponent, Point<int>()), | |||
| image (im), owner (ddc), | |||
| image (im), | |||
| owner (ddc), | |||
| mouseDragSource (draggingSource->getComponentUnderMouse()), | |||
| imageOffset (transformOffsetCoordinates (sourceComponent, offset)), | |||
| originalInputSourceIndex (draggingSource->getIndex()), | |||
| @@ -83,7 +84,7 @@ public: | |||
| g.fillAll (Colours::white); | |||
| g.setOpacity (1.0f); | |||
| g.drawImageAt (image, 0, 0); | |||
| g.drawImage (image.getImage(), getLocalBounds().toFloat()); | |||
| } | |||
| void mouseUp (const MouseEvent& e) override | |||
| @@ -164,7 +165,7 @@ public: | |||
| forceMouseCursorUpdate(); | |||
| } | |||
| void updateImage (const Image& newImage) | |||
| void updateImage (const ScaledImage& newImage) | |||
| { | |||
| image = newImage; | |||
| updateSize(); | |||
| @@ -218,7 +219,7 @@ public: | |||
| DragAndDropTarget::SourceDetails sourceDetails; | |||
| private: | |||
| Image image; | |||
| ScaledImage image; | |||
| DragAndDropContainer& owner; | |||
| WeakReference<Component> mouseDragSource, currentlyOverComp; | |||
| const Point<int> imageOffset; | |||
| @@ -229,7 +230,8 @@ private: | |||
| void updateSize() | |||
| { | |||
| setSize (image.getWidth(), image.getHeight()); | |||
| const auto bounds = image.getScaledBounds().toNearestInt(); | |||
| setSize (bounds.getWidth(), bounds.getHeight()); | |||
| } | |||
| void forceMouseCursorUpdate() | |||
| @@ -388,17 +390,13 @@ private: | |||
| //============================================================================== | |||
| DragAndDropContainer::DragAndDropContainer() | |||
| { | |||
| } | |||
| DragAndDropContainer::DragAndDropContainer() = default; | |||
| DragAndDropContainer::~DragAndDropContainer() | |||
| { | |||
| } | |||
| DragAndDropContainer::~DragAndDropContainer() = default; | |||
| void DragAndDropContainer::startDragging (const var& sourceDescription, | |||
| Component* sourceComponent, | |||
| Image dragImage, | |||
| const ScaledImage& dragImage, | |||
| const bool allowDraggingToExternalWindows, | |||
| const Point<int>* imageOffsetFromMouse, | |||
| const MouseInputSource* inputSourceCausingDrag) | |||
| @@ -414,55 +412,53 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, | |||
| return; | |||
| } | |||
| auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt(); | |||
| Point<int> imageOffset; | |||
| const auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt(); | |||
| if (dragImage.isNull()) | |||
| struct ImageAndOffset | |||
| { | |||
| dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds()) | |||
| .convertedToFormat (Image::ARGB); | |||
| ScaledImage image; | |||
| Point<double> offset; | |||
| }; | |||
| dragImage.multiplyAllAlphas (0.6f); | |||
| const auto imageToUse = [&]() -> ImageAndOffset | |||
| { | |||
| if (! dragImage.getImage().isNull()) | |||
| return { dragImage, imageOffsetFromMouse != nullptr ? dragImage.getScaledBounds().getConstrainedPoint (-imageOffsetFromMouse->toDouble()) | |||
| : dragImage.getScaledBounds().getCentre() }; | |||
| auto lo = 150; | |||
| auto hi = 400; | |||
| const auto scaleFactor = 2.0; | |||
| auto image = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds(), true, (float) scaleFactor) | |||
| .convertedToFormat (Image::ARGB); | |||
| image.multiplyAllAlphas (0.6f); | |||
| auto relPos = sourceComponent->getLocalPoint (nullptr, lastMouseDown); | |||
| auto clipped = dragImage.getBounds().getConstrainedPoint (relPos); | |||
| Random random; | |||
| const auto relPos = sourceComponent->getLocalPoint (nullptr, lastMouseDown).toDouble(); | |||
| const auto clipped = (image.getBounds().toDouble() / scaleFactor).getConstrainedPoint (relPos); | |||
| for (auto y = dragImage.getHeight(); --y >= 0;) | |||
| { | |||
| auto dy = (y - clipped.getY()) * (y - clipped.getY()); | |||
| Image fade (Image::SingleChannel, image.getWidth(), image.getHeight(), true); | |||
| Graphics fadeContext (fade); | |||
| for (auto x = dragImage.getWidth(); --x >= 0;) | |||
| { | |||
| auto dx = x - clipped.getX(); | |||
| auto distance = roundToInt (std::sqrt (dx * dx + dy)); | |||
| ColourGradient gradient; | |||
| gradient.isRadial = true; | |||
| gradient.point1 = clipped.toFloat() * scaleFactor; | |||
| gradient.point2 = gradient.point1 + Point<float> (0.0f, scaleFactor * 400.0f); | |||
| gradient.addColour (0.0, Colours::white); | |||
| gradient.addColour (0.375, Colours::white); | |||
| gradient.addColour (1.0, Colours::transparentWhite); | |||
| if (distance > lo) | |||
| { | |||
| auto alpha = (distance > hi) ? 0 | |||
| : (float) (hi - distance) / (float) (hi - lo) | |||
| + random.nextFloat() * 0.008f; | |||
| fadeContext.setGradientFill (gradient); | |||
| fadeContext.fillAll(); | |||
| dragImage.multiplyAlphaAt (x, y, alpha); | |||
| } | |||
| } | |||
| } | |||
| Image composite (Image::ARGB, image.getWidth(), image.getHeight(), true); | |||
| Graphics compositeContext (composite); | |||
| imageOffset = clipped; | |||
| } | |||
| else | |||
| { | |||
| if (imageOffsetFromMouse == nullptr) | |||
| imageOffset = dragImage.getBounds().getCentre(); | |||
| else | |||
| imageOffset = dragImage.getBounds().getConstrainedPoint (-*imageOffsetFromMouse); | |||
| } | |||
| compositeContext.reduceClipRegion (fade, {}); | |||
| compositeContext.drawImageAt (image, 0, 0); | |||
| return { ScaledImage (composite, scaleFactor), clipped }; | |||
| }(); | |||
| auto* dragImageComponent = dragImageComponents.add (new DragImageComponent (dragImage, sourceDescription, sourceComponent, | |||
| draggingSource, *this, imageOffset)); | |||
| auto* dragImageComponent = dragImageComponents.add (new DragImageComponent (imageToUse.image, sourceDescription, sourceComponent, | |||
| draggingSource, *this, imageToUse.offset.roundToInt())); | |||
| if (allowDraggingToExternalWindows) | |||
| { | |||
| @@ -527,7 +523,7 @@ var DragAndDropContainer::getDragDescriptionForIndex (int index) const | |||
| return dragImageComponents.getUnchecked (index)->sourceDetails.description; | |||
| } | |||
| void DragAndDropContainer::setCurrentDragImage (const Image& newImage) | |||
| void DragAndDropContainer::setCurrentDragImage (const ScaledImage& newImage) | |||
| { | |||
| // If you are performing drag and drop in a multi-touch environment then | |||
| // you should use the setDragImageForIndex() method instead! | |||
| @@ -536,7 +532,7 @@ void DragAndDropContainer::setCurrentDragImage (const Image& newImage) | |||
| dragImageComponents[0]->updateImage (newImage); | |||
| } | |||
| void DragAndDropContainer::setDragImageForIndex (int index, const Image& newImage) | |||
| void DragAndDropContainer::setDragImageForIndex (int index, const ScaledImage& newImage) | |||
| { | |||
| if (isPositiveAndBelow (index, dragImageComponents.size())) | |||
| dragImageComponents.getUnchecked (index)->updateImage (newImage); | |||
| @@ -94,11 +94,27 @@ public: | |||
| */ | |||
| void startDragging (const var& sourceDescription, | |||
| Component* sourceComponent, | |||
| Image dragImage = Image(), | |||
| const ScaledImage& dragImage = ScaledImage(), | |||
| bool allowDraggingToOtherJuceWindows = false, | |||
| const Point<int>* imageOffsetFromMouse = nullptr, | |||
| const MouseInputSource* inputSourceCausingDrag = nullptr); | |||
| [[deprecated ("This overload does not allow the image's scale to be specified. Use the other overload of startDragging instead.")]] | |||
| void startDragging (const var& sourceDescription, | |||
| Component* sourceComponent, | |||
| Image dragImage, | |||
| bool allowDraggingToOtherJuceWindows = false, | |||
| const Point<int>* imageOffsetFromMouse = nullptr, | |||
| const MouseInputSource* inputSourceCausingDrag = nullptr) | |||
| { | |||
| startDragging (sourceDescription, | |||
| sourceComponent, | |||
| ScaledImage (dragImage), | |||
| allowDraggingToOtherJuceWindows, | |||
| imageOffsetFromMouse, | |||
| inputSourceCausingDrag); | |||
| } | |||
| /** Returns true if something is currently being dragged. */ | |||
| bool isDragAndDropActive() const; | |||
| @@ -130,13 +146,19 @@ public: | |||
| @see setDragImageForIndex | |||
| */ | |||
| void setCurrentDragImage (const Image& newImage); | |||
| void setCurrentDragImage (const ScaledImage& newImage); | |||
| [[deprecated ("This overload does not allow the image's scale to be specified. Use the other overload of setCurrentDragImage instead.")]] | |||
| void setCurrentDragImage (const Image& newImage) { setCurrentDragImage (ScaledImage (newImage)); } | |||
| /** Same as the setCurrentDragImage() method but takes a touch index parameter. | |||
| @see setCurrentDragImage | |||
| */ | |||
| void setDragImageForIndex (int index, const Image& newImage); | |||
| */ | |||
| void setDragImageForIndex (int index, const ScaledImage& newImage); | |||
| [[deprecated ("This overload does not allow the image's scale to be specified. Use the other overload of setDragImageForIndex instead.")]] | |||
| void setDragImageForIndex (int index, const Image& newImage) { setDragImageForIndex (index, ScaledImage (newImage)); } | |||
| /** Utility to find the DragAndDropContainer for a given Component. | |||
| @@ -1040,7 +1040,7 @@ void ListBox::repaintRow (const int rowNumber) noexcept | |||
| repaint (getRowPosition (rowNumber, true)); | |||
| } | |||
| Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY) | |||
| ScaledImage ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, int& imageY) | |||
| { | |||
| Rectangle<int> imageArea; | |||
| auto firstRow = getRowContainingPosition (0, viewport->getY()); | |||
| @@ -1062,7 +1062,8 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in | |||
| imageX = imageArea.getX(); | |||
| imageY = imageArea.getY(); | |||
| auto listScale = Component::getApproximateScaleFactorForComponent (this); | |||
| const auto additionalScale = 2.0f; | |||
| const auto listScale = Component::getApproximateScaleFactorForComponent (this) * additionalScale; | |||
| Image snapshot (Image::ARGB, | |||
| roundToInt ((float) imageArea.getWidth() * listScale), | |||
| roundToInt ((float) imageArea.getHeight() * listScale), | |||
| @@ -1075,9 +1076,9 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in | |||
| if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i)) | |||
| { | |||
| Graphics g (snapshot); | |||
| g.setOrigin (getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition()); | |||
| g.setOrigin ((getLocalPoint (rowComp, Point<int>()) - imageArea.getPosition()) * additionalScale); | |||
| auto rowScale = Component::getApproximateScaleFactorForComponent (rowComp); | |||
| const auto rowScale = Component::getApproximateScaleFactorForComponent (rowComp) * additionalScale; | |||
| if (g.reduceClipRegion (rowComp->getLocalBounds() * rowScale)) | |||
| { | |||
| @@ -1090,7 +1091,7 @@ Image ListBox::createSnapshotOfRows (const SparseSet<int>& rows, int& imageX, in | |||
| } | |||
| } | |||
| return snapshot; | |||
| return { snapshot, additionalScale }; | |||
| } | |||
| void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet<int>& rowsToDrag, const var& dragDescription, bool allowDraggingToOtherWindows) | |||
| @@ -538,7 +538,7 @@ public: | |||
| @see Component::createComponentSnapshot | |||
| */ | |||
| virtual Image createSnapshotOfRows (const SparseSet<int>& rows, int& x, int& y); | |||
| virtual ScaledImage createSnapshotOfRows (const SparseSet<int>& rows, int& x, int& y); | |||
| /** Returns the viewport that this ListBox uses. | |||
| @@ -74,7 +74,7 @@ public: | |||
| if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this)) | |||
| { | |||
| dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image(), true, nullptr, &e.source); | |||
| dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), ScaledImage(), true, nullptr, &e.source); | |||
| if (ToolbarItemComponent* const tc = getToolbarItemComponent()) | |||
| { | |||
| @@ -476,14 +476,15 @@ private: | |||
| { | |||
| pos.setSize (pos.getWidth(), item.itemHeight); | |||
| const auto additionalScale = 2.0f; | |||
| auto dragImage = Component::createComponentSnapshot (pos, | |||
| true, | |||
| Component::getApproximateScaleFactorForComponent (itemComponent)); | |||
| Component::getApproximateScaleFactorForComponent (itemComponent) * additionalScale); | |||
| dragImage.multiplyAllAlphas (0.6f); | |||
| auto imageOffset = pos.getPosition() - e.getPosition(); | |||
| dragContainer->startDragging (dragDescription, &owner, dragImage, true, &imageOffset, &e.source); | |||
| dragContainer->startDragging (dragDescription, &owner, { dragImage, additionalScale }, true, &imageOffset, &e.source); | |||
| scopedScrollDisabler = std::make_unique<ScopedDisableViewportScroll> (*itemComponent); | |||
| } | |||