diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index ea958c7791..9f3b75f86c 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -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. diff --git a/modules/juce_graphics/images/juce_ScaledImage.h b/modules/juce_graphics/images/juce_ScaledImage.h new file mode 100644 index 0000000000..fb1790b977 --- /dev/null +++ b/modules/juce_graphics/images/juce_ScaledImage.h @@ -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 getScaledBounds() const { return image.getBounds().toDouble() / scaleFactor; } + +private: + Image image; + double scaleFactor = 1.0; +}; + +} // namespace juce diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index 850ca2eedd..d76a352945 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -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" diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 2202bbc6fd..dd5633a958 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -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 offset) : sourceDetails (desc, sourceComponent, Point()), - 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 mouseDragSource, currentlyOverComp; const Point 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* imageOffsetFromMouse, const MouseInputSource* inputSourceCausingDrag) @@ -414,55 +412,53 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, return; } - auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt(); - Point imageOffset; + const auto lastMouseDown = draggingSource->getLastMouseDownPosition().roundToInt(); - if (dragImage.isNull()) + struct ImageAndOffset { - dragImage = sourceComponent->createComponentSnapshot (sourceComponent->getLocalBounds()) - .convertedToFormat (Image::ARGB); + ScaledImage image; + Point 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 (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); diff --git a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h index 4be1ca6c9c..ef38dc0891 100644 --- a/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h +++ b/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.h @@ -94,11 +94,27 @@ public: */ void startDragging (const var& sourceDescription, Component* sourceComponent, - Image dragImage = Image(), + const ScaledImage& dragImage = ScaledImage(), bool allowDraggingToOtherJuceWindows = false, const Point* 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* 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. diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/modules/juce_gui_basics/widgets/juce_ListBox.cpp index 9831293b8d..5c98a310e1 100644 --- a/modules/juce_gui_basics/widgets/juce_ListBox.cpp +++ b/modules/juce_gui_basics/widgets/juce_ListBox.cpp @@ -1040,7 +1040,7 @@ void ListBox::repaintRow (const int rowNumber) noexcept repaint (getRowPosition (rowNumber, true)); } -Image ListBox::createSnapshotOfRows (const SparseSet& rows, int& imageX, int& imageY) +ScaledImage ListBox::createSnapshotOfRows (const SparseSet& rows, int& imageX, int& imageY) { Rectangle imageArea; auto firstRow = getRowContainingPosition (0, viewport->getY()); @@ -1062,7 +1062,8 @@ Image ListBox::createSnapshotOfRows (const SparseSet& 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& rows, int& imageX, in if (auto* rowComp = viewport->getComponentForRowIfOnscreen (firstRow + i)) { Graphics g (snapshot); - g.setOrigin (getLocalPoint (rowComp, Point()) - imageArea.getPosition()); + g.setOrigin ((getLocalPoint (rowComp, Point()) - 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& rows, int& imageX, in } } - return snapshot; + return { snapshot, additionalScale }; } void ListBox::startDragAndDrop (const MouseEvent& e, const SparseSet& rowsToDrag, const var& dragDescription, bool allowDraggingToOtherWindows) diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.h b/modules/juce_gui_basics/widgets/juce_ListBox.h index ae8ae8434c..34532c66dd 100644 --- a/modules/juce_gui_basics/widgets/juce_ListBox.h +++ b/modules/juce_gui_basics/widgets/juce_ListBox.h @@ -538,7 +538,7 @@ public: @see Component::createComponentSnapshot */ - virtual Image createSnapshotOfRows (const SparseSet& rows, int& x, int& y); + virtual ScaledImage createSnapshotOfRows (const SparseSet& rows, int& x, int& y); /** Returns the viewport that this ListBox uses. diff --git a/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp b/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp index 9bf7d6b2eb..e42f0e6195 100644 --- a/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp +++ b/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp @@ -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()) { diff --git a/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/modules/juce_gui_basics/widgets/juce_TreeView.cpp index 8c9c24d214..1aaedce921 100644 --- a/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -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 (*itemComponent); }