diff --git a/extras/JuceDemo/Source/demos/OpenGLDemo.cpp b/extras/JuceDemo/Source/demos/OpenGLDemo.cpp index e4f0ffbac459..f7573a1b1435 100644 --- a/extras/JuceDemo/Source/demos/OpenGLDemo.cpp +++ b/extras/JuceDemo/Source/demos/OpenGLDemo.cpp @@ -109,14 +109,8 @@ class DemoOpenGLCanvas : public Component, { OpenGLHelpers::clear (Colours::darkgrey.withAlpha (1.0f)); - { - MessageManagerLock mm (Thread::getCurrentThread()); - if (! mm.lockWasGained()) - return; - - updateTextureImage(); // this will update our dynamically-changing texture image. - drawBackground2DStuff(); // draws some 2D content to demonstrate the OpenGLGraphicsContext class - } + updateTextureImage(); // this will update our dynamically-changing texture image. + drawBackground2DStuff(); // draws some 2D content to demonstrate the OpenGLGraphicsContext class // Having used the juce 2D renderer, it will have messed-up a whole load of GL state, so // we'll put back any important settings before doing our normal GL 3D drawing.. @@ -127,8 +121,8 @@ class DemoOpenGLCanvas : public Component, glEnable (GL_TEXTURE_2D); #if JUCE_USE_OPENGL_FIXED_FUNCTION - OpenGLHelpers::prepareFor2D (getWidth(), getHeight()); - OpenGLHelpers::setPerspective (45.0, getWidth() / (double) getHeight(), 0.1, 100.0); + OpenGLHelpers::prepareFor2D (getContextWidth(), getContextHeight()); + OpenGLHelpers::setPerspective (45.0, getContextWidth() / (double) getContextHeight(), 0.1, 100.0); glTranslatef (0.0f, 0.0f, -5.0f); draggableOrientation.applyToOpenGLMatrix(); @@ -173,11 +167,14 @@ class DemoOpenGLCanvas : public Component, void drawBackground2DStuff() { // Create an OpenGLGraphicsContext that will draw into this GL window.. - ScopedPointer glRenderer (createOpenGLGraphicsContext (openGLContext)); + ScopedPointer glRenderer (createOpenGLGraphicsContext (openGLContext, + getContextWidth(), + getContextHeight())); if (glRenderer != nullptr) { Graphics g (glRenderer); + g.addTransform (AffineTransform::scale ((float) getScale())); // This stuff just creates a spinning star shape and fills it.. Path p; @@ -194,6 +191,10 @@ class DemoOpenGLCanvas : public Component, } } + double getScale() const { return Desktop::getInstance().getDisplays().getDisplayContaining (getScreenBounds().getCentre()).scale; } + int getContextWidth() const { return roundToInt (getScale() * getWidth()); } + int getContextHeight() const { return roundToInt (getScale() * getHeight()); } + void timerCallback() { rotation += (float) speedSlider.getValue(); diff --git a/modules/juce_graphics/geometry/juce_AffineTransform.cpp b/modules/juce_graphics/geometry/juce_AffineTransform.cpp index 51d5a2587cfc..8cdfd28e5e33 100644 --- a/modules/juce_graphics/geometry/juce_AffineTransform.cpp +++ b/modules/juce_graphics/geometry/juce_AffineTransform.cpp @@ -149,8 +149,12 @@ AffineTransform AffineTransform::scaled (const float factorX, const float factor AffineTransform AffineTransform::scale (const float factorX, const float factorY) noexcept { - return AffineTransform (factorX, 0, 0, - 0, factorY, 0); + return AffineTransform (factorX, 0, 0, 0, factorY, 0); +} + +AffineTransform AffineTransform::scale (const float factor) noexcept +{ + return AffineTransform (factor, 0, 0, 0, factor, 0); } AffineTransform AffineTransform::scaled (const float factorX, const float factorY, diff --git a/modules/juce_graphics/geometry/juce_AffineTransform.h b/modules/juce_graphics/geometry/juce_AffineTransform.h index 984c992e5b5f..b28d6d532636 100644 --- a/modules/juce_graphics/geometry/juce_AffineTransform.h +++ b/modules/juce_graphics/geometry/juce_AffineTransform.h @@ -173,6 +173,9 @@ class JUCE_API AffineTransform static AffineTransform scale (float factorX, float factorY) noexcept; + /** Returns a new transform which is a re-scale about the origin. */ + static AffineTransform scale (float factor) noexcept; + /** Returns a new transform which is a re-scale centred around the point provided. */ static AffineTransform scale (float factorX, float factorY, float pivotX, float pivotY) noexcept; diff --git a/modules/juce_graphics/geometry/juce_Rectangle.h b/modules/juce_graphics/geometry/juce_Rectangle.h index c44c0e3783ed..1b9bcdf5b782 100644 --- a/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/modules/juce_graphics/geometry/juce_Rectangle.h @@ -292,6 +292,25 @@ class Rectangle return *this; } + /** Scales this rectangle by the given amount, centred around the origin. */ + template + Rectangle operator* (FloatType scaleFactor) const noexcept + { + Rectangle r (*this); + r *= scaleFactor; + return r; + } + + /** Scales this rectangle by the given amount, centred around the origin. */ + template + Rectangle operator*= (FloatType scaleFactor) noexcept + { + pos *= scaleFactor; + w *= scaleFactor; + h *= scaleFactor; + return *this; + } + /** Expands the rectangle by a given amount. Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2). diff --git a/modules/juce_graphics/native/juce_android_Fonts.cpp b/modules/juce_graphics/native/juce_android_Fonts.cpp index fa2b7846c7c8..76a5c8e4a8c2 100644 --- a/modules/juce_graphics/native/juce_android_Fonts.cpp +++ b/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -186,7 +186,7 @@ class AndroidTypeface : public Typeface { JNIEnv* env = getEnv(); - jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (unitsToHeightScaleFactor, unitsToHeightScaleFactor).followedBy (t)); + jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (unitsToHeightScaleFactor).followedBy (t)); jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, (jchar) glyphNumber, paint.get(), matrix, rect.get()); env->DeleteLocalRef (matrix); diff --git a/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp index 708837f22568..576a8ae15fa0 100644 --- a/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp +++ b/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -178,7 +178,7 @@ class WindowsDirectWriteTypeface : public Typeface const float pathAscent = (1024.0f * dwFontMetrics.ascent) / designUnitsPerEm; const float pathDescent = (1024.0f * dwFontMetrics.descent) / designUnitsPerEm; const float pathScale = 1.0f / (std::abs (pathAscent) + std::abs (pathDescent)); - pathTransform = AffineTransform::scale (pathScale, pathScale); + pathTransform = AffineTransform::scale (pathScale); } float getAscent() const { return ascent; } diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp index a63b93f1a92a..5cc64c81ae54 100644 --- a/modules/juce_gui_basics/components/juce_Component.cpp +++ b/modules/juce_gui_basics/components/juce_Component.cpp @@ -1911,12 +1911,12 @@ void Component::paintEntireComponent (Graphics& g, const bool ignoreAlphaLevel) (int) (scale * getWidth()), (int) (scale * getHeight()), ! flags.opaqueFlag); { Graphics g2 (effectImage); - g2.addTransform (AffineTransform::scale (scale, scale)); + g2.addTransform (AffineTransform::scale (scale)); paintComponentAndChildren (g2); } g.saveState(); - g.addTransform (AffineTransform::scale (1.0f / scale, 1.0f / scale)); + g.addTransform (AffineTransform::scale (1.0f / scale)); effect->applyEffect (effectImage, g, scale, ignoreAlphaLevel ? 1.0f : getAlpha()); g.restoreState(); } diff --git a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 44525d254c27..3ff396993ef3 100644 --- a/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -1097,7 +1097,7 @@ class SVGState else if (t.startsWithIgnoreCase ("scale")) { if (tokens.size() == 1) - trans = AffineTransform::scale (numbers[0], numbers[0]); + trans = AffineTransform::scale (numbers[0]); else trans = AffineTransform::scale (numbers[0], numbers[1]); } diff --git a/modules/juce_opengl/native/juce_OpenGL_osx.h b/modules/juce_opengl/native/juce_OpenGL_osx.h index f06632a7c28c..966b61c223f9 100644 --- a/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -43,6 +43,12 @@ struct ThreadSafeNSOpenGLViewClass : public ObjCClass static void init (id self) { object_setInstanceVariable (self, "lock", new CriticalSection()); + + #if defined (MAC_OS_X_VERSION_10_7) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) + if ([self respondsToSelector: @selector (setWantsBestResolutionOpenGLSurface:)]) + [self setWantsBestResolutionOpenGLSurface: YES]; + #endif + setNeedsUpdate (self, YES); } diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 651310a4f4b0..291c56df18ab 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -27,12 +27,11 @@ class OpenGLContext::CachedImage : public CachedComponentImage, public Thread { public: - CachedImage (OpenGLContext& context_, - Component& component_, - const OpenGLPixelFormat& pixelFormat, - void* contextToShareWith) + CachedImage (OpenGLContext& c, Component& comp, + const OpenGLPixelFormat& pixelFormat, void* contextToShareWith) : Thread ("OpenGL Rendering"), - context (context_), component (component_), + context (c), component (comp), + scale (1.0), #if JUCE_OPENGL_ES shadersAvailable (true), #else @@ -102,14 +101,14 @@ class OpenGLContext::CachedImage : public CachedComponentImage, } //============================================================================== - bool ensureFrameBufferSize (int width, int height) + bool ensureFrameBufferSize() { const int fbW = cachedImageFrameBuffer.getWidth(); const int fbH = cachedImageFrameBuffer.getHeight(); - if (fbW != width || fbH != height || ! cachedImageFrameBuffer.isValid()) + if (fbW != viewportArea.getWidth() || fbH != viewportArea.getHeight() || ! cachedImageFrameBuffer.isValid()) { - if (! cachedImageFrameBuffer.initialise (context, width, height)) + if (! cachedImageFrameBuffer.initialise (context, viewportArea.getWidth(), viewportArea.getHeight())) return false; validArea.clear(); @@ -119,18 +118,19 @@ class OpenGLContext::CachedImage : public CachedComponentImage, return true; } - void clearRegionInFrameBuffer (const RectangleList& list) + void clearRegionInFrameBuffer (const RectangleList& list, const double scale) { glClearColor (0, 0, 0, 0); glEnable (GL_SCISSOR_TEST); const GLuint previousFrameBufferTarget = OpenGLFrameBuffer::getCurrentFrameBufferTarget(); cachedImageFrameBuffer.makeCurrentRenderingTarget(); + const int imageH = cachedImageFrameBuffer.getHeight(); - for (RectangleList::Iterator i (list); i.next();) + for (const Rectangle* i = list.begin(), * const e = list.end(); i != e; ++i) { - const Rectangle& r = *i.getRectangle(); - glScissor (r.getX(), component.getHeight() - r.getBottom(), r.getWidth(), r.getHeight()); + const Rectangle r (*i * scale); + glScissor (r.getX(), imageH - r.getBottom(), r.getWidth(), r.getHeight()); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } @@ -141,13 +141,21 @@ class OpenGLContext::CachedImage : public CachedComponentImage, bool renderFrame() { + ScopedPointer mmLock; + if (context.renderComponents && needsUpdate) + { + mmLock = new MessageManagerLock (this); // need to acquire this before locking the context. + if (! mmLock->lockWasGained()) + return false; + } + if (! context.makeActive()) return false; NativeContext::Locker locker (*nativeContext); JUCE_CHECK_OPENGL_ERROR - glViewport (0, 0, component.getWidth(), component.getHeight()); + glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight()); if (context.renderer != nullptr) { @@ -156,51 +164,76 @@ class OpenGLContext::CachedImage : public CachedComponentImage, } if (context.renderComponents) - paintComponent(); + { + if (needsUpdate) + { + needsUpdate = false; + paintComponent(); + mmLock = nullptr; + } + + glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight()); + drawComponentBuffer(); + } context.swapBuffers(); return true; } - void paintComponent() + void updateViewportSize (bool canTriggerUpdate) { - if (needsUpdate) + const double newScale = Desktop::getInstance().getDisplays() + .getDisplayContaining (component.getScreenBounds().getCentre()).scale; + + Rectangle newArea (roundToInt (component.getWidth() * newScale), + roundToInt (component.getHeight() * newScale)); + + if (scale != newScale || viewportArea != newArea) { - MessageManagerLock mm (this); - if (! mm.lockWasGained()) - return; + scale = newScale; + viewportArea = newArea; - needsUpdate = false; + if (canTriggerUpdate) + invalidateAll(); + } + } - // you mustn't set your own cached image object when attaching a GL context! - jassert (get (component) == this); + void paintComponent() + { + // you mustn't set your own cached image object when attaching a GL context! + jassert (get (component) == this); - const Rectangle bounds (component.getLocalBounds()); - if (! ensureFrameBufferSize (bounds.getWidth(), bounds.getHeight())) - return; + updateViewportSize (false); - RectangleList invalid (bounds); - invalid.subtract (validArea); - validArea = bounds; + if (! ensureFrameBufferSize()) + return; - if (! invalid.isEmpty()) - { - clearRegionInFrameBuffer (invalid); + RectangleList invalid (viewportArea); + invalid.subtract (validArea); + validArea = viewportArea; - { - ScopedPointer g (createOpenGLGraphicsContext (context, cachedImageFrameBuffer)); - g->clipToRectangleList (invalid); - paintOwner (*g); - JUCE_CHECK_OPENGL_ERROR - } + if (! invalid.isEmpty()) + { + clearRegionInFrameBuffer (invalid, scale); + + { + ScopedPointer g (createOpenGLGraphicsContext (context, cachedImageFrameBuffer)); + g->addTransform (AffineTransform::scale ((float) scale)); + g->clipToRectangleList (invalid); - if (! context.isActive()) - context.makeActive(); + paintOwner (*g); + JUCE_CHECK_OPENGL_ERROR } - JUCE_CHECK_OPENGL_ERROR + if (! context.isActive()) + context.makeActive(); } + JUCE_CHECK_OPENGL_ERROR + } + + void drawComponentBuffer() + { #if ! JUCE_ANDROID glEnable (GL_TEXTURE_2D); clearGLError(); @@ -209,7 +242,7 @@ class OpenGLContext::CachedImage : public CachedComponentImage, glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID()); const Rectangle cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight()); - context.copyTexture (cacheBounds, cacheBounds, context.getWidth(), context.getHeight()); + context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight()); glBindTexture (GL_TEXTURE_2D, 0); JUCE_CHECK_OPENGL_ERROR } @@ -316,6 +349,8 @@ class OpenGLContext::CachedImage : public CachedComponentImage, OpenGLFrameBuffer cachedImageFrameBuffer; RectangleList validArea; + Rectangle viewportArea; + double scale; StringArray associatedObjectNames; ReferenceCountedArray associatedObjects; @@ -333,11 +368,10 @@ void OpenGLContext::NativeContext::contextCreatedCallback() { isInsideGLCallback = true; - CachedImage* const c = CachedImage::get (component); - jassert (c != nullptr); - - if (c != nullptr) + if (CachedImage* const c = CachedImage::get (component)) c->initialiseOnThread(); + else + jassertfalse; isInsideGLCallback = false; } @@ -357,8 +391,8 @@ void OpenGLContext::NativeContext::renderCallback() class OpenGLContext::Attachment : public ComponentMovementWatcher { public: - Attachment (OpenGLContext& context_, Component& comp) - : ComponentMovementWatcher (&comp), context (context_) + Attachment (OpenGLContext& c, Component& comp) + : ComponentMovementWatcher (&comp), context (c) { if (canBeAttached (comp)) attach(); @@ -376,12 +410,12 @@ class OpenGLContext::Attachment : public ComponentMovementWatcher if (isAttached (comp) != canBeAttached (comp)) componentVisibilityChanged(); - context.width = comp.getWidth(); - context.height = comp.getHeight(); - if (comp.getWidth() > 0 && comp.getHeight() > 0 && context.nativeContext != nullptr) { + if (CachedImage* const c = CachedImage::get (comp)) + c->updateViewportSize (true); + context.nativeContext->updateWindowPosition (comp.getTopLevelComponent() ->getLocalArea (&comp, comp.getLocalBounds())); } @@ -458,7 +492,7 @@ class OpenGLContext::Attachment : public ComponentMovementWatcher //============================================================================== OpenGLContext::OpenGLContext() : nativeContext (nullptr), renderer (nullptr), contextToShareWith (nullptr), - width (0), height (0), renderComponents (true) + renderComponents (true) { } @@ -510,10 +544,6 @@ void OpenGLContext::attachTo (Component& component) if (getTargetComponent() != &component) { detach(); - - width = component.getWidth(); - height = component.getHeight(); - attachment = new Attachment (*this, component); } } @@ -522,7 +552,6 @@ void OpenGLContext::detach() { attachment = nullptr; nativeContext = nullptr; - width = height = 0; } bool OpenGLContext::isAttached() const noexcept diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.h b/modules/juce_opengl/opengl/juce_OpenGLContext.h index db021062a23d..fe9eccecc019 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.h +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.h @@ -129,14 +129,7 @@ class JUCE_API OpenGLContext /** Asynchronously causes a repaint to be made. */ void triggerRepaint(); - //============================================================================== - /** Returns the width of this context */ - inline int getWidth() const noexcept { return width; } - - /** Returns the height of this context */ - inline int getHeight() const noexcept { return height; } - /** If this context is backed by a frame buffer, this returns its ID number, or 0 if the context does not use a framebuffer. */ @@ -245,7 +238,6 @@ class JUCE_API OpenGLContext ScopedPointer attachment; OpenGLPixelFormat pixelFormat; void* contextToShareWith; - int width, height; bool renderComponents; CachedImage* getCachedImage() const noexcept; diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp index 9b1a52c03a3a..02bbc90210e4 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp @@ -991,15 +991,15 @@ struct StateHelpers void add (const RectangleList& list, const PixelARGB& colour) noexcept { - for (RectangleList::Iterator i (list); i.next();) - add (*i.getRectangle(), colour); + for (const Rectangle* i = list.begin(), * const e = list.end(); i != e; ++i) + add (*i, colour); } void add (const RectangleList& list, const Rectangle& clip, const PixelARGB& colour) noexcept { - for (RectangleList::Iterator i (list); i.next();) + for (const Rectangle* i = list.begin(), * const e = list.end(); i != e; ++i) { - const Rectangle r (i.getRectangle()->getIntersection (clip)); + const Rectangle r (i->getIntersection (clip)); if (! r.isEmpty()) add (r, colour); @@ -1714,9 +1714,9 @@ class ClipRegion_RectangleList : public ClipRegionBase const PixelARGB colour (fill.colour.getPixelARGB()); ShaderFillOperation fillOp (*this, fill, false, false); - for (RectangleList::Iterator i (clip); i.next();) + for (const Rectangle* i = clip.begin(), * const e = clip.end(); i != e; ++i) { - const Rectangle r (i.getRectangle()->toFloat().getIntersection (area)); + const Rectangle r (i->toFloat().getIntersection (area)); if (! r.isEmpty()) state.shaderQuadQueue.add (r, colour); } @@ -1832,6 +1832,11 @@ class SavedState cloneClipIfMultiplyReferenced(); clip = clip->clipToRectangle (transform.translated (r)); } + else if (transform.isIntegerScaling) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToRectangle (transform.transformed (r)); + } else { Path p; @@ -1854,6 +1859,16 @@ class SavedState offsetList.offsetAll (transform.xOffset, transform.yOffset); clip = clip->clipToRectangleList (offsetList); } + else if (transform.isIntegerScaling) + { + cloneClipIfMultiplyReferenced(); + RectangleList scaledList; + + for (const Rectangle* i = r.begin(), * const e = r.end(); i != e; ++i) + scaledList.add (transform.transformed (*i)); + + clip = clip->clipToRectangleList (scaledList); + } else { clipToPath (r.toPath(), AffineTransform::identity); @@ -2234,20 +2249,18 @@ LowLevelGraphicsContext* createOpenGLContext (const Target& target) } //============================================================================== -LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& context) +LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& context, int width, int height) { - return createOpenGLGraphicsContext (context, context.getFrameBufferID(), - context.getWidth(), context.getHeight()); + return createOpenGLGraphicsContext (context, context.getFrameBufferID(), width, height); } LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& context, OpenGLFrameBuffer& target) { - using namespace OpenGLRendering; - return createOpenGLContext (Target (context, target, Point())); + return OpenGLRendering::createOpenGLContext (OpenGLRendering::Target (context, target, Point())); } LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& context, unsigned int frameBufferID, int width, int height) { using namespace OpenGLRendering; - return createOpenGLContext (Target (context, frameBufferID, width, height)); + return OpenGLRendering::createOpenGLContext (OpenGLRendering::Target (context, frameBufferID, width, height)); } diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.h b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.h index 2814f7f42b4c..cf5362841f5f 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.h +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.h @@ -30,7 +30,8 @@ /** Creates a graphics context object that will render into the given OpenGL target. The caller is responsible for deleting this object when no longer needed. */ -LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& target); +LowLevelGraphicsContext* createOpenGLGraphicsContext (OpenGLContext& target, + int width, int height); /** Creates a graphics context object that will render into the given OpenGL target. The caller is responsible for deleting this object when no longer needed.