From 2c2a11dee9eb54253dd3493f0534e2f685ea9592 Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Sun, 21 Nov 2010 16:53:03 +0000 Subject: [PATCH] Added the ability to apply affine transforms to components - Component::setTransform(). Added a slider to the widgets demo to show this in action. Changed Component::reallyContains() to take a rectangle instead of raw coordinates. --- extras/juce demo/Source/demos/WidgetsDemo.cpp | 21 +- juce_amalgamated.cpp | 226 +++++++++++------- juce_amalgamated.h | 150 +++++++++--- src/core/juce_StandardHeader.h | 2 +- src/gui/components/buttons/juce_Button.cpp | 55 ++--- src/gui/components/buttons/juce_Button.h | 3 +- src/gui/components/controls/juce_ComboBox.cpp | 2 +- src/gui/components/controls/juce_Slider.cpp | 4 + .../controls/juce_TableHeaderComponent.cpp | 2 +- src/gui/components/juce_Component.cpp | 148 ++++++++---- src/gui/components/juce_Component.h | 146 ++++++++--- .../menus/juce_MenuBarComponent.cpp | 2 +- src/gui/components/menus/juce_PopupMenu.cpp | 10 +- .../mouse/juce_MouseHoverDetector.cpp | 2 +- .../special/juce_MidiKeyboardComponent.cpp | 2 +- 15 files changed, 533 insertions(+), 242 deletions(-) diff --git a/extras/juce demo/Source/demos/WidgetsDemo.cpp b/extras/juce demo/Source/demos/WidgetsDemo.cpp index 8c647b178c..337edd22c6 100644 --- a/extras/juce demo/Source/demos/WidgetsDemo.cpp +++ b/extras/juce demo/Source/demos/WidgetsDemo.cpp @@ -1088,7 +1088,8 @@ public: //============================================================================== class WidgetsDemo : public Component, - public ButtonListener + public ButtonListener, + public SliderListener { public: //============================================================================== @@ -1111,9 +1112,17 @@ public: //============================================================================== addAndMakeVisible (&enableButton); enableButton.setBounds (230, 10, 180, 24); - enableButton.setTooltip ("toggle button"); + enableButton.setTooltip ("Enables/disables all the components"); enableButton.setToggleState (true, false); enableButton.addButtonListener (this); + + addAndMakeVisible (&transformSlider); + transformSlider.setSliderStyle (Slider::LinearBar); + transformSlider.setTextValueSuffix (" degrees rotation"); + transformSlider.setRange (-180.0, 180.0, 0.1); + transformSlider.setBounds (440, 10, 180, 24); + transformSlider.setTooltip ("Applies a transform to the components"); + transformSlider.addListener (this); } ~WidgetsDemo() @@ -1397,9 +1406,17 @@ public: } } + void sliderValueChanged (Slider*) + { + // When you move the roation slider, we'll apply a rotaion transform to the whole tabs component.. + tabs.setTransform (AffineTransform::rotation ((float) (transformSlider.getValue() / (180.0 / double_Pi)), + getWidth() * 0.5f, getHeight() * 0.5f)); + } + private: TextButton menuButton; ToggleButton enableButton; + Slider transformSlider; DemoTabbedComponent tabs; }; diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 30f4962e1e..447877fc1c 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -39782,7 +39782,7 @@ END_JUCE_NAMESPACE /*** Start of inlined file: juce_Component.cpp ***/ BEGIN_JUCE_NAMESPACE -#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); +#define CHECK_MESSAGE_MANAGER_IS_LOCKED jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); Component* Component::currentlyFocusedComponent = 0; @@ -39982,22 +39982,34 @@ public: static const Point convertFromParentSpace (const Component& comp, const Point& pointInParentSpace) { - return pointInParentSpace - comp.getPosition(); + if (comp.affineTransform_ == 0) + return pointInParentSpace - comp.getPosition(); + + return pointInParentSpace.toFloat().transformedBy (comp.affineTransform_->inverted()).toInt() - comp.getPosition(); } static const Rectangle convertFromParentSpace (const Component& comp, const Rectangle& areaInParentSpace) { - return areaInParentSpace - comp.getPosition(); + if (comp.affineTransform_ == 0) + return areaInParentSpace - comp.getPosition(); + + return areaInParentSpace.toFloat().transformed (comp.affineTransform_->inverted()).getSmallestIntegerContainer() - comp.getPosition(); } static const Point convertToParentSpace (const Component& comp, const Point& pointInLocalSpace) { - return pointInLocalSpace + comp.getPosition(); + if (comp.affineTransform_ == 0) + return pointInLocalSpace + comp.getPosition(); + + return (pointInLocalSpace + comp.getPosition()).toFloat().transformedBy (*comp.affineTransform_).toInt(); } static const Rectangle convertToParentSpace (const Component& comp, const Rectangle& areaInLocalSpace) { - return areaInLocalSpace + comp.getPosition(); + if (comp.affineTransform_ == 0) + return areaInLocalSpace + comp.getPosition(); + + return (areaInLocalSpace + comp.getPosition()).toFloat().transformed (*comp.affineTransform_).getSmallestIntegerContainer(); } template @@ -40070,8 +40082,7 @@ public: { const Component& child = *comp.childComponentList_.getUnchecked(i); -//xxx if (child.isVisible() && ! child.isTransformed()) - if (child.isVisible()) + if (child.isVisible() && ! child.isTransformed()) { const Rectangle newClip (clipRect.getIntersection (child.bounds_)); @@ -40195,7 +40206,7 @@ void Component::setName (const String& name) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (componentName_ != name) { @@ -40221,7 +40232,7 @@ void Component::setVisible (bool shouldBeVisible) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED SafePointer safePointer (this); @@ -40309,7 +40320,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (isOpaque()) styleWanted &= ~ComponentPeer::windowIsSemiTransparent; @@ -40397,7 +40408,7 @@ void Component::removeFromDesktop() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { @@ -40472,7 +40483,7 @@ void Component::toFront (const bool setAsForeground) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { @@ -40711,7 +40722,7 @@ void Component::setBounds (const int x, const int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (w < 0) w = 0; if (h < 0) h = 0; @@ -40899,6 +40910,41 @@ void Component::setBoundsToFit (int x, int y, int width, int height, } } +bool Component::isTransformed() const throw() +{ + return affineTransform_ != 0; +} + +void Component::setTransform (const AffineTransform& newTransform) +{ + if (newTransform.isIdentity()) + { + if (affineTransform_ != 0) + { + repaint(); + affineTransform_ = 0; + repaint(); + } + } + else if (affineTransform_ == 0) + { + repaint(); + affineTransform_ = new AffineTransform (newTransform); + repaint(); + } + else if (*affineTransform_ != newTransform) + { + repaint(); + *affineTransform_ = newTransform; + repaint(); + } +} + +const AffineTransform Component::getTransform() const +{ + return affineTransform_ != 0 ? *affineTransform_ : AffineTransform::identity; +} + bool Component::hitTest (int x, int y) { if (! flags.ignoresMouseClicksFlag) @@ -40953,15 +40999,13 @@ bool Component::contains (const Point& point) return false; } -bool Component::reallyContains (const int x, const int y, const bool returnTrueIfWithinAChild) +bool Component::reallyContains (const Point& point, const bool returnTrueIfWithinAChild) { - const Point p (x, y); - - if (! contains (p)) + if (! contains (point)) return false; Component* const top = getTopLevelComponent(); - const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, p)); + const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, point)); return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition)); } @@ -40994,7 +41038,7 @@ void Component::addChildComponent (Component* const child, int zOrder) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (child != 0 && child->parentComponent_ != this) { @@ -41047,7 +41091,7 @@ Component* Component::removeChildComponent (const int index) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED Component* const child = childComponentList_ [index]; @@ -41215,7 +41259,7 @@ void Component::enterModalState (const bool takeKeyboardFocus_, ModalComponentMa { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED // Check for an attempt to make a component modal when it already is! // This can cause nasty problems.. @@ -41385,7 +41429,7 @@ void Component::internalRepaint (int x, int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (x < 0) { @@ -41412,7 +41456,17 @@ void Component::internalRepaint (int x, int y, int w, int h) if (parentComponent_ != 0) { if (parentComponent_->flags.visibleFlag) - parentComponent_->internalRepaint (x + getX(), y + getY(), w, h); + { + if (affineTransform_ == 0) + { + parentComponent_->internalRepaint (x + getX(), y + getY(), w, h); + } + else + { + const Rectangle r (ComponentHelpers::convertToParentSpace (*this, Rectangle (x, y, w, h))); + parentComponent_->internalRepaint (r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + } } else if (flags.hasHeavyweightPeerFlag) { @@ -41447,6 +41501,15 @@ void Component::paintComponent (Graphics& g) } } +void Component::paintTransformedChild (Graphics& g) +{ + if (affineTransform_ != 0) + g.addTransform (*affineTransform_); + + g.setOrigin (getX(), getY()); + paintEntireComponent (g, false); +} + void Component::paintComponentAndChildren (Graphics& g) { const Rectangle clipBounds (g.getClipBounds()); @@ -41476,8 +41539,7 @@ void Component::paintComponentAndChildren (Graphics& g) if (child->flags.dontClipGraphicsFlag) { - g.setOrigin (child->getX(), child->getY()); - child->paintEntireComponent (g, false); + child->paintTransformedChild (g); } else { @@ -41497,10 +41559,7 @@ void Component::paintComponentAndChildren (Graphics& g) } if (nothingClipped || ! g.isClipEmpty()) - { - g.setOrigin (child->getX(), child->getY()); - child->paintEntireComponent (g, false); - } + child->paintTransformedChild (g); } } @@ -41860,7 +41919,7 @@ void Component::addMouseListener (MouseListener* const newListener, { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED // If you register a component as a mouselistener for itself, it'll receive all the events // twice - once via the direct callback that all components get anyway, and then again as a listener! @@ -41876,7 +41935,7 @@ void Component::removeMouseListener (MouseListener* const listenerToRemove) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (mouseListeners_ != 0) mouseListeners_->removeListener (listenerToRemove); @@ -42131,7 +42190,7 @@ void Component::internalMouseDrag (MouseInputSource& source, const Point& r { Desktop& desktop = Desktop::getInstance(); - flags.mouseOverFlag = reallyContains (relativePos.getX(), relativePos.getY(), false); + flags.mouseOverFlag = reallyContains (relativePos, false); BailOutChecker checker (this); @@ -42499,7 +42558,7 @@ void Component::grabKeyboardFocus() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED grabFocusInternal (focusChangedDirectly); } @@ -42508,7 +42567,7 @@ void Component::moveKeyboardFocusToSibling (const bool moveToNext) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (parentComponent_ != 0) { @@ -42563,20 +42622,9 @@ void Component::giveAwayFocus() componentLosingFocus->internalFocusLoss (focusChangedDirectly); } -bool Component::isMouseOver() const throw() -{ - return flags.mouseOverFlag; -} - -bool Component::isMouseButtonDown() const throw() -{ - return flags.mouseDownFlag; -} - -bool Component::isMouseOverOrDragging() const throw() -{ - return flags.mouseOverFlag || flags.mouseDownFlag; -} +bool Component::isMouseOver() const throw() { return flags.mouseOverFlag; } +bool Component::isMouseButtonDown() const throw() { return flags.mouseDownFlag; } +bool Component::isMouseOverOrDragging() const throw() { return flags.mouseOverFlag || flags.mouseDownFlag; } bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() throw() { @@ -42590,8 +42638,7 @@ const Point Component::getMouseXYRelative() const const Rectangle Component::getParentMonitorArea() const { - return Desktop::getInstance() - .getMonitorAreaContaining (localPointToGlobal (getLocalBounds().getCentre())); + return Desktop::getInstance().getMonitorAreaContaining (getScreenBounds().getCentre()); } void Component::addKeyListener (KeyListener* const newListener) @@ -43515,34 +43562,29 @@ void Button::turnOffOtherButtonsInGroup (const bool sendChangeNotification) void Button::enablementChanged() { - updateState (0); + updateState(); repaint(); } -Button::ButtonState Button::updateState (const MouseEvent* const e) +Button::ButtonState Button::updateState() +{ + return updateState (reallyContains (getMouseXYRelative(), true), isMouseButtonDown()); +} + +Button::ButtonState Button::updateState (const bool over, const bool down) { - ButtonState state = buttonNormal; + ButtonState newState = buttonNormal; if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent()) { - Point mousePos; - - if (e == 0) - mousePos = getMouseXYRelative(); - else - mousePos = e->getEventRelativeTo (this).getPosition(); - - const bool over = reallyContains (mousePos.getX(), mousePos.getY(), true); - const bool down = isMouseButtonDown(); - if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown) - state = buttonDown; + newState = buttonDown; else if (over) - state = buttonOver; + newState = buttonOver; } - setState (state); - return state; + setState (newState); + return newState; } void Button::setState (const ButtonState newState) @@ -43687,19 +43729,19 @@ void Button::paint (Graphics& g) paintButton (g, isOver(), isDown()); } -void Button::mouseEnter (const MouseEvent& e) +void Button::mouseEnter (const MouseEvent&) { - updateState (&e); + updateState (true, false); } -void Button::mouseExit (const MouseEvent& e) +void Button::mouseExit (const MouseEvent&) { - updateState (&e); + updateState (false, false); } void Button::mouseDown (const MouseEvent& e) { - updateState (&e); + updateState (true, true); if (isDown()) { @@ -43714,7 +43756,7 @@ void Button::mouseDown (const MouseEvent& e) void Button::mouseUp (const MouseEvent& e) { const bool wasDown = isDown(); - updateState (&e); + updateState (isMouseOver(), false); if (wasDown && isOver() && ! triggerOnMouseDown) internalClickCallback (e.mods); @@ -43723,7 +43765,7 @@ void Button::mouseUp (const MouseEvent& e) void Button::mouseDrag (const MouseEvent& e) { const ButtonState oldState = buttonState; - updateState (&e); + updateState (isMouseOver(), true); if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) getRepeatTimer().startTimer (autoRepeatSpeed); @@ -43731,13 +43773,13 @@ void Button::mouseDrag (const MouseEvent& e) void Button::focusGained (FocusChangeType) { - updateState (0); + updateState(); repaint(); } void Button::focusLost (FocusChangeType) { - updateState (0); + updateState(); repaint(); } @@ -43750,7 +43792,7 @@ void Button::setVisible (bool shouldBeVisible) if (! shouldBeVisible) needsToRelease = false; - updateState (0); + updateState(); } else { @@ -43878,7 +43920,7 @@ bool Button::keyStateChanged (const bool, Component*) if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown)) getRepeatTimer().startTimer (autoRepeatDelay); - updateState (0); + updateState(); if (isEnabled() && wasDown && ! isKeyDown) { @@ -43922,10 +43964,10 @@ void Button::repeatTimerCallback() if (needsRepainting) { getRepeatTimer().stopTimer(); - updateState (0); + updateState(); needsRepainting = false; } - else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState (0) == buttonDown))) + else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown))) { int repeatSpeed = autoRepeatSpeed; @@ -48020,7 +48062,7 @@ void ComboBox::mouseUp (const MouseEvent& e2) const MouseEvent e (e2.getEventRelativeTo (this)); - if (reallyContains (e.x, e.y, true) + if (reallyContains (e.getPosition(), true) && (e2.eventComponent == this || ! label->isEditable())) { showPopup(); @@ -49804,6 +49846,10 @@ void Slider::lookAndFeelChanged() valueBox->setTooltip (getTooltip()); } + else + { + valueBox = 0; + } if (style == IncDecButtons) { @@ -51750,7 +51796,7 @@ int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const void TableHeaderComponent::updateColumnUnderMouse (int x, int y) { - const int newCol = (reallyContains (x, y, true) && getResizeDraggerAt (x) == 0) + const int newCol = (reallyContains (Point (x, y), true) && getResizeDraggerAt (x) == 0) ? getColumnIdAtX (x) : 0; if (newCol != columnIdUnderMouse) @@ -68454,7 +68500,7 @@ int MenuBarComponent::getItemAt (const int x, const int y) { for (int i = 0; i < xPositions.size(); ++i) if (x >= xPositions[i] && x < xPositions[i + 1]) - return reallyContains (x, y, true) ? i : -1; + return reallyContains (Point (x, y), true) ? i : -1; return -1; } @@ -69243,7 +69289,7 @@ public: // comp that we're attached to. const Point mousePos (componentAttachedTo->getMouseXYRelative()); - if (componentAttachedTo->reallyContains (mousePos.getX(), mousePos.getY(), true)) + if (componentAttachedTo->reallyContains (mousePos, true)) { postCommandMessage (PopupMenuSettings::dismissCommandId); // dismiss asynchrounously return; @@ -69287,7 +69333,7 @@ public: const uint32 now = Time::getMillisecondCounter(); if (now > timeEnteredCurrentChildComp + 100 - && reallyContains (localMousePos.getX(), localMousePos.getY(), true) + && reallyContains (localMousePos, true) && currentChild != 0 && (! disableMouseMoves) && ! (activeSubMenu != 0 && activeSubMenu->isVisible())) @@ -69379,7 +69425,7 @@ public: else if (wasDown && now > menuCreationTime + 250 && ! (isDown || overScrollArea)) { - isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); + isOver = reallyContains (localMousePos, true); if (isOver) { @@ -69452,7 +69498,7 @@ private: void updateMouseOverStatus (const Point& globalMousePos) { const Point relPos (getLocalPoint (0, globalMousePos)); - isOver = reallyContains (relPos.getX(), relPos.getY(), true); + isOver = reallyContains (relPos, true); if (activeSubMenu != 0) activeSubMenu->updateMouseOverStatus (globalMousePos); @@ -69798,7 +69844,7 @@ private: void highlightItemUnderMouse (const Point& globalMousePos, const Point& localMousePos) { - isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); + isOver = reallyContains (localMousePos, true); if (isOver) hasBeenOver = true; @@ -71732,7 +71778,7 @@ void MouseHoverDetector::hoverTimerCallback() { const Point pos (source->getMouseXYRelative()); - if (source->reallyContains (pos.getX(), pos.getY(), false)) + if (source->reallyContains (pos, false)) { hasJustHovered = true; mouseHovered (pos.getX(), pos.getY()); @@ -75390,7 +75436,7 @@ const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 }; int MidiKeyboardComponent::xyToNote (const Point& pos, float& mousePositionVelocity) { - if (! reallyContains (pos.getX(), pos.getY(), false)) + if (! reallyContains (pos, false)) return -1; Point p (pos); diff --git a/juce_amalgamated.h b/juce_amalgamated.h index a12922aa19..b73098cf57 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -64,7 +64,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 94 +#define JUCE_BUILDNUMBER 95 /** Current Juce version number. @@ -26113,15 +26113,21 @@ public: */ bool isAlwaysOnTop() const throw(); - /** Returns the x co-ordinate of the component's left edge. + /** Returns the x coordinate of the component's left edge. This is a distance in pixels from the left edge of the component's parent. - @see getScreenX + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ inline int getX() const throw() { return bounds_.getX(); } - /** Returns the y co-ordinate of the top of this component. + /** Returns the y coordinate of the top of this component. This is a distance in pixels from the top edge of the component's parent. - @see getScreenY + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ inline int getY() const throw() { return bounds_.getY(); } @@ -26131,26 +26137,38 @@ public: /** Returns the component's height in pixels. */ inline int getHeight() const throw() { return bounds_.getHeight(); } - /** Returns the x co-ordinate of the component's right-hand edge. + /** Returns the x coordinate of the component's right-hand edge. This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ int getRight() const throw() { return bounds_.getRight(); } /** Returns the component's top-left position as a Point. */ const Point getPosition() const throw() { return bounds_.getPosition(); } - /** Returns the y co-ordinate of the bottom edge of this component. + /** Returns the y coordinate of the bottom edge of this component. This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ int getBottom() const throw() { return bounds_.getBottom(); } /** Returns this component's bounding box. The rectangle returned is relative to the top-left of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ const Rectangle& getBounds() const throw() { return bounds_; } /** Returns the component's bounds, relative to its own origin. - This is like getBounds(), but returns the rectangle in local co-ordinates, In practice, it'll + This is like getBounds(), but returns the rectangle in local coordinates, In practice, it'll return a rectangle with position (0, 0), and the same size as this component. */ const Rectangle getLocalBounds() const throw(); @@ -26166,12 +26184,12 @@ public: void getVisibleArea (RectangleList& result, bool includeSiblings) const; - /** Returns this component's x co-ordinate relative the the screen's top-left origin. + /** Returns this component's x coordinate relative the the screen's top-left origin. @see getX, localPointToGlobal */ int getScreenX() const; - /** Returns this component's y co-ordinate relative the the screen's top-left origin. + /** Returns this component's y coordinate relative the the screen's top-left origin. @see getY, localPointToGlobal */ int getScreenY() const; @@ -26200,6 +26218,10 @@ public: This takes a rectangle that is relative to a different component, and returns its position relative to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. */ const Rectangle getLocalArea (const Component* sourceComponent, const Rectangle& areaRelativeToSourceComponent) const; @@ -26210,6 +26232,10 @@ public: const Point localPointToGlobal (const Point& localPoint) const; /** Converts a rectangle from this component's coordinate space to a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. @see getLocalPoint, localPointToGlobal */ const Rectangle localAreaToGlobal (const Rectangle& localArea) const; @@ -26221,6 +26247,10 @@ public: If the component actually moves, this method will make a synchronous call to moved(). + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setBounds, ComponentListener::componentMovedOrResized */ void setTopLeftPosition (int x, int y); @@ -26231,29 +26261,51 @@ public: The position is relative to the top-left of the component's parent. If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. */ void setTopRightPosition (int x, int y); /** Changes the size of the component. A synchronous call to resized() will be occur if the size actually changes. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. */ void setSize (int newWidth, int newHeight); /** Changes the component's position and size. - The co-ordinates are relative to the top-left of the component's parent, or relative + The coordinates are relative to the top-left of the component's parent, or relative to the origin of the screen is the component is on the desktop. If this method changes the component's top-left position, it will make a synchronous call to moved(). If it changes the size, it will also make a call to resized(). + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setTopLeftPosition, setSize, ComponentListener::componentMovedOrResized */ void setBounds (int x, int y, int width, int height); /** Changes the component's position and size. + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen is the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setBounds */ void setBounds (const Rectangle& newBounds); @@ -26264,6 +26316,8 @@ public: setBoundsRelative (0.2f, 0.2f, 0.5f, 0.5f) would give it half the width and height of the parent, with its top-left position 20% of the way across and down the parent. + + @see setBounds */ void setBoundsRelative (float proportionalX, float proportionalY, float proportionalWidth, float proportionalHeight); @@ -26272,6 +26326,8 @@ public: This will position the component within its parent, leaving the specified number of pixels around each edge. + + @see setBounds */ void setBoundsInset (const BorderSize& borders); @@ -26286,6 +26342,8 @@ public: It will then be positioned within the rectangle according to the justification flags specified. + + @see setBounds */ void setBoundsToFit (int x, int y, int width, int height, const Justification& justification, @@ -26295,6 +26353,8 @@ public: Leaves the component's size unchanged, but sets the position of its centre relative to its parent's top-left. + + @see setBounds */ void setCentrePosition (int x, int y); @@ -26314,6 +26374,37 @@ public: */ void centreWithSize (int width, int height); + /** Sets a transform matrix to be applied to this component. + + If you set a transform for a component, the component's position will be warped by it, relative to + the component's parent's top-left origin. This means that the values you pass into setBounds() will no + longer reflect the actual area within the parent that the component covers, as the bounds will be + transformed and the component will probably end up actually appearing somewhere else within its parent. + + When using transforms you need to be extremely careful when converting coordinates between the + coordinate spaces of different components or the screen - you should always use getLocalPoint(), + getLocalArea(), etc to do this, and never just manually add a component's position to a point in order to + convert it between different components (but I'm sure you would never have done that anyway...). + + Currently, transforms are not supported for desktop windows, so the transform will be ignored if you + put a component on the desktop. + + To remove a component's transform, simply pass AffineTransform::identity as the parameter to this method. + */ + void setTransform (const AffineTransform& transform); + + /** Returns the transform that is currently being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + const AffineTransform getTransform() const; + + /** Returns true if a non-identity transform is being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + bool isTransformed() const throw(); + /** Returns a proportion of the component's width. This is a handy equivalent of (getWidth() * proportion). @@ -26340,7 +26431,7 @@ public: */ int getParentHeight() const throw(); - /** Returns the screen co-ordinates of the monitor that contains this component. + /** Returns the screen coordinates of the monitor that contains this component. If there's only one monitor, this will return its size - if there are multiple monitors, it will return the area of the monitor that contains the component's @@ -26509,7 +26600,7 @@ public: Overriding this method allows you to create components which only intercept mouse-clicks within a user-defined area. - This is called to find out whether a particular x, y co-ordinate is + This is called to find out whether a particular x, y coordinate is considered to be inside the component or not, and is used by methods such as contains() and getComponentAt() to work out which component the mouse is clicked on. @@ -26531,10 +26622,10 @@ public: Note that for components on the desktop, this method will be ignored, because it's not always possible to implement this behaviour on all platforms. - @param x the x co-ordinate to test, relative to the left hand edge of this + @param x the x coordinate to test, relative to the left hand edge of this component. This value is guaranteed to be greater than or equal to zero, and less than the component's width - @param y the y co-ordinate to test, relative to the top edge of this + @param y the y coordinate to test, relative to the top edge of this component. This value is guaranteed to be greater than or equal to zero, and less than the component's height @returns true if the click is considered to be inside the component @@ -26576,32 +26667,29 @@ public: Never override this method! Use hitTest to create custom hit regions. - @param point the x co-ordinate to test, relative to this component's top-left. + @param localPoint the coordinate to test, relative to this component's top-left. @returns true if the point is within the component's hit-test area, but only if that part of the component isn't clipped by its parent component. Note that this won't take into account any overlapping sibling components which might be in the way - for that, see reallyContains() @see hitTest, reallyContains, getComponentAt */ - bool contains (const Point& point); + bool contains (const Point& localPoint); /** Returns true if a given point lies in this component, taking any overlapping siblings into account. - @param x the x co-ordinate to test, relative to this component's left hand edge. - @param y the y co-ordinate to test, relative to this component's top edge. - @param returnTrueIfWithinAChild if the point actually lies within a child of this - component, this determines the value that will - be returned. - + @param localPoint the coordinate to test, relative to this component's top-left. + @param returnTrueIfWithinAChild if the point actually lies within a child of this component, + this determines whether that is counted as a hit. @see contains, getComponentAt */ - bool reallyContains (int x, int y, bool returnTrueIfWithinAChild); + bool reallyContains (const Point& localPoint, bool returnTrueIfWithinAChild); /** Returns the component at a certain point within this one. - @param x the x co-ordinate to test, relative to this component's left hand edge. - @param y the y co-ordinate to test, relative to this component's top edge. + @param x the x coordinate to test, relative to this component's left edge. + @param y the y coordinate to test, relative to this component's top edge. @returns the component that is at this position - which may be 0, this component, or one of its children. Note that overlapping siblings that might actually be in the way are not taken into account by this method - to account for these, @@ -26612,7 +26700,7 @@ public: /** Returns the component at a certain point within this one. - @param position the co-ordinates to test, relative to this component's top-left. + @param position the coordinate to test, relative to this component's top-left. @returns the component that is at this position - which may be 0, this component, or one of its children. Note that overlapping siblings that might actually be in the way are not taken into account by this method - to account for these, @@ -27442,8 +27530,7 @@ public: static bool JUCE_CALLTYPE isMouseButtonDownAnywhere() throw(); /** Returns the mouse's current position, relative to this component. - - The co-ordinates are relative to the component's top-left corner. + The return value is relative to the component's top-left corner. */ const Point getMouseXYRelative() const; @@ -27849,6 +27936,7 @@ private: String componentName_; Component* parentComponent_; Rectangle bounds_; + ScopedPointer affineTransform_; Array childComponentList_; LookAndFeel* lookAndFeel_; MouseCursor cursor_; @@ -27914,6 +28002,7 @@ private: void internalHierarchyChanged(); void paintComponentAndChildren (Graphics& g); void paintComponent (Graphics& g); + void paintTransformedChild (Graphics& g); void sendMovedResizedMessages (bool wasMoved, bool wasResized); void repaintParent(); void sendFakeMouseMove() const; @@ -36206,7 +36295,8 @@ private: void repeatTimerCallback(); RepeatTimer& getRepeatTimer(); - ButtonState updateState (const MouseEvent*); + ButtonState updateState(); + ButtonState updateState (bool isOver, bool isDown); bool isShortcutPressed() const; void turnOffOtherButtonsInGroup (bool sendChangeNotification); diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index b446fb9e43..3b9b8f2db8 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 52 -#define JUCE_BUILDNUMBER 94 +#define JUCE_BUILDNUMBER 95 /** Current Juce version number. diff --git a/src/gui/components/buttons/juce_Button.cpp b/src/gui/components/buttons/juce_Button.cpp index 1d3ecf73ef..abaed71e7f 100644 --- a/src/gui/components/buttons/juce_Button.cpp +++ b/src/gui/components/buttons/juce_Button.cpp @@ -227,34 +227,29 @@ void Button::turnOffOtherButtonsInGroup (const bool sendChangeNotification) //============================================================================== void Button::enablementChanged() { - updateState (0); + updateState(); repaint(); } -Button::ButtonState Button::updateState (const MouseEvent* const e) +Button::ButtonState Button::updateState() { - ButtonState state = buttonNormal; + return updateState (reallyContains (getMouseXYRelative(), true), isMouseButtonDown()); +} + +Button::ButtonState Button::updateState (const bool over, const bool down) +{ + ButtonState newState = buttonNormal; if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent()) { - Point mousePos; - - if (e == 0) - mousePos = getMouseXYRelative(); - else - mousePos = e->getEventRelativeTo (this).getPosition(); - - const bool over = reallyContains (mousePos.getX(), mousePos.getY(), true); - const bool down = isMouseButtonDown(); - if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown) - state = buttonDown; + newState = buttonDown; else if (over) - state = buttonOver; + newState = buttonOver; } - setState (state); - return state; + setState (newState); + return newState; } void Button::setState (const ButtonState newState) @@ -403,19 +398,19 @@ void Button::paint (Graphics& g) } //============================================================================== -void Button::mouseEnter (const MouseEvent& e) +void Button::mouseEnter (const MouseEvent&) { - updateState (&e); + updateState (true, false); } -void Button::mouseExit (const MouseEvent& e) +void Button::mouseExit (const MouseEvent&) { - updateState (&e); + updateState (false, false); } void Button::mouseDown (const MouseEvent& e) { - updateState (&e); + updateState (true, true); if (isDown()) { @@ -430,7 +425,7 @@ void Button::mouseDown (const MouseEvent& e) void Button::mouseUp (const MouseEvent& e) { const bool wasDown = isDown(); - updateState (&e); + updateState (isMouseOver(), false); if (wasDown && isOver() && ! triggerOnMouseDown) internalClickCallback (e.mods); @@ -439,7 +434,7 @@ void Button::mouseUp (const MouseEvent& e) void Button::mouseDrag (const MouseEvent& e) { const ButtonState oldState = buttonState; - updateState (&e); + updateState (isMouseOver(), true); if (autoRepeatDelay >= 0 && buttonState != oldState && isDown()) getRepeatTimer().startTimer (autoRepeatSpeed); @@ -447,13 +442,13 @@ void Button::mouseDrag (const MouseEvent& e) void Button::focusGained (FocusChangeType) { - updateState (0); + updateState(); repaint(); } void Button::focusLost (FocusChangeType) { - updateState (0); + updateState(); repaint(); } @@ -467,7 +462,7 @@ void Button::setVisible (bool shouldBeVisible) if (! shouldBeVisible) needsToRelease = false; - updateState (0); + updateState(); } else { @@ -597,7 +592,7 @@ bool Button::keyStateChanged (const bool, Component*) if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown)) getRepeatTimer().startTimer (autoRepeatDelay); - updateState (0); + updateState(); if (isEnabled() && wasDown && ! isKeyDown) { @@ -642,10 +637,10 @@ void Button::repeatTimerCallback() if (needsRepainting) { getRepeatTimer().stopTimer(); - updateState (0); + updateState(); needsRepainting = false; } - else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState (0) == buttonDown))) + else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown))) { int repeatSpeed = autoRepeatSpeed; diff --git a/src/gui/components/buttons/juce_Button.h b/src/gui/components/buttons/juce_Button.h index c210b39212..4110d1f305 100644 --- a/src/gui/components/buttons/juce_Button.h +++ b/src/gui/components/buttons/juce_Button.h @@ -495,7 +495,8 @@ private: void repeatTimerCallback(); RepeatTimer& getRepeatTimer(); - ButtonState updateState (const MouseEvent*); + ButtonState updateState(); + ButtonState updateState (bool isOver, bool isDown); bool isShortcutPressed() const; void turnOffOtherButtonsInGroup (bool sendChangeNotification); diff --git a/src/gui/components/controls/juce_ComboBox.cpp b/src/gui/components/controls/juce_ComboBox.cpp index b8557154b6..f967c8f545 100644 --- a/src/gui/components/controls/juce_ComboBox.cpp +++ b/src/gui/components/controls/juce_ComboBox.cpp @@ -590,7 +590,7 @@ void ComboBox::mouseUp (const MouseEvent& e2) const MouseEvent e (e2.getEventRelativeTo (this)); - if (reallyContains (e.x, e.y, true) + if (reallyContains (e.getPosition(), true) && (e2.eventComponent == this || ! label->isEditable())) { showPopup(); diff --git a/src/gui/components/controls/juce_Slider.cpp b/src/gui/components/controls/juce_Slider.cpp index 18172c5b7d..eb5359f2c9 100644 --- a/src/gui/components/controls/juce_Slider.cpp +++ b/src/gui/components/controls/juce_Slider.cpp @@ -352,6 +352,10 @@ void Slider::lookAndFeelChanged() valueBox->setTooltip (getTooltip()); } + else + { + valueBox = 0; + } if (style == IncDecButtons) { diff --git a/src/gui/components/controls/juce_TableHeaderComponent.cpp b/src/gui/components/controls/juce_TableHeaderComponent.cpp index 0b3990ed75..2aafd4aeb4 100644 --- a/src/gui/components/controls/juce_TableHeaderComponent.cpp +++ b/src/gui/components/controls/juce_TableHeaderComponent.cpp @@ -909,7 +909,7 @@ int TableHeaderComponent::getResizeDraggerAt (const int mouseX) const void TableHeaderComponent::updateColumnUnderMouse (int x, int y) { - const int newCol = (reallyContains (x, y, true) && getResizeDraggerAt (x) == 0) + const int newCol = (reallyContains (Point (x, y), true) && getResizeDraggerAt (x) == 0) ? getColumnIdAtX (x) : 0; if (newCol != columnIdUnderMouse) diff --git a/src/gui/components/juce_Component.cpp b/src/gui/components/juce_Component.cpp index de11d28ccc..588663eade 100644 --- a/src/gui/components/juce_Component.cpp +++ b/src/gui/components/juce_Component.cpp @@ -44,7 +44,7 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -#define checkMessageManagerIsLocked jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); +#define CHECK_MESSAGE_MANAGER_IS_LOCKED jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); Component* Component::currentlyFocusedComponent = 0; @@ -249,22 +249,34 @@ public: static const Point convertFromParentSpace (const Component& comp, const Point& pointInParentSpace) { - return pointInParentSpace - comp.getPosition(); + if (comp.affineTransform_ == 0) + return pointInParentSpace - comp.getPosition(); + + return pointInParentSpace.toFloat().transformedBy (comp.affineTransform_->inverted()).toInt() - comp.getPosition(); } static const Rectangle convertFromParentSpace (const Component& comp, const Rectangle& areaInParentSpace) { - return areaInParentSpace - comp.getPosition(); + if (comp.affineTransform_ == 0) + return areaInParentSpace - comp.getPosition(); + + return areaInParentSpace.toFloat().transformed (comp.affineTransform_->inverted()).getSmallestIntegerContainer() - comp.getPosition(); } static const Point convertToParentSpace (const Component& comp, const Point& pointInLocalSpace) { - return pointInLocalSpace + comp.getPosition(); + if (comp.affineTransform_ == 0) + return pointInLocalSpace + comp.getPosition(); + + return (pointInLocalSpace + comp.getPosition()).toFloat().transformedBy (*comp.affineTransform_).toInt(); } static const Rectangle convertToParentSpace (const Component& comp, const Rectangle& areaInLocalSpace) { - return areaInLocalSpace + comp.getPosition(); + if (comp.affineTransform_ == 0) + return areaInLocalSpace + comp.getPosition(); + + return (areaInLocalSpace + comp.getPosition()).toFloat().transformed (*comp.affineTransform_).getSmallestIntegerContainer(); } template @@ -337,8 +349,7 @@ public: { const Component& child = *comp.childComponentList_.getUnchecked(i); -//xxx if (child.isVisible() && ! child.isTransformed()) - if (child.isVisible()) + if (child.isVisible() && ! child.isTransformed()) { const Rectangle newClip (clipRect.getIntersection (child.bounds_)); @@ -465,7 +476,7 @@ void Component::setName (const String& name) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (componentName_ != name) { @@ -491,7 +502,7 @@ void Component::setVisible (bool shouldBeVisible) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED SafePointer safePointer (this); @@ -582,7 +593,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (isOpaque()) styleWanted &= ~ComponentPeer::windowIsSemiTransparent; @@ -670,7 +681,7 @@ void Component::removeFromDesktop() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { @@ -748,7 +759,7 @@ void Component::toFront (const bool setAsForeground) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (flags.hasHeavyweightPeerFlag) { @@ -990,7 +1001,7 @@ void Component::setBounds (const int x, const int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (w < 0) w = 0; if (h < 0) h = 0; @@ -1178,6 +1189,42 @@ void Component::setBoundsToFit (int x, int y, int width, int height, } } +//============================================================================== +bool Component::isTransformed() const throw() +{ + return affineTransform_ != 0; +} + +void Component::setTransform (const AffineTransform& newTransform) +{ + if (newTransform.isIdentity()) + { + if (affineTransform_ != 0) + { + repaint(); + affineTransform_ = 0; + repaint(); + } + } + else if (affineTransform_ == 0) + { + repaint(); + affineTransform_ = new AffineTransform (newTransform); + repaint(); + } + else if (*affineTransform_ != newTransform) + { + repaint(); + *affineTransform_ = newTransform; + repaint(); + } +} + +const AffineTransform Component::getTransform() const +{ + return affineTransform_ != 0 ? *affineTransform_ : AffineTransform::identity; +} + //============================================================================== bool Component::hitTest (int x, int y) { @@ -1233,15 +1280,13 @@ bool Component::contains (const Point& point) return false; } -bool Component::reallyContains (const int x, const int y, const bool returnTrueIfWithinAChild) +bool Component::reallyContains (const Point& point, const bool returnTrueIfWithinAChild) { - const Point p (x, y); - - if (! contains (p)) + if (! contains (point)) return false; Component* const top = getTopLevelComponent(); - const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, p)); + const Component* const compAtPosition = top->getComponentAt (top->getLocalPoint (this, point)); return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition)); } @@ -1275,7 +1320,7 @@ void Component::addChildComponent (Component* const child, int zOrder) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (child != 0 && child->parentComponent_ != this) { @@ -1328,7 +1373,7 @@ Component* Component::removeChildComponent (const int index) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED Component* const child = childComponentList_ [index]; @@ -1500,7 +1545,7 @@ void Component::enterModalState (const bool takeKeyboardFocus_, ModalComponentMa { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED // Check for an attempt to make a component modal when it already is! // This can cause nasty problems.. @@ -1674,7 +1719,7 @@ void Component::internalRepaint (int x, int y, int w, int h) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (x < 0) { @@ -1701,7 +1746,17 @@ void Component::internalRepaint (int x, int y, int w, int h) if (parentComponent_ != 0) { if (parentComponent_->flags.visibleFlag) - parentComponent_->internalRepaint (x + getX(), y + getY(), w, h); + { + if (affineTransform_ == 0) + { + parentComponent_->internalRepaint (x + getX(), y + getY(), w, h); + } + else + { + const Rectangle r (ComponentHelpers::convertToParentSpace (*this, Rectangle (x, y, w, h))); + parentComponent_->internalRepaint (r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + } } else if (flags.hasHeavyweightPeerFlag) { @@ -1737,6 +1792,15 @@ void Component::paintComponent (Graphics& g) } } +void Component::paintTransformedChild (Graphics& g) +{ + if (affineTransform_ != 0) + g.addTransform (*affineTransform_); + + g.setOrigin (getX(), getY()); + paintEntireComponent (g, false); +} + void Component::paintComponentAndChildren (Graphics& g) { const Rectangle clipBounds (g.getClipBounds()); @@ -1766,8 +1830,7 @@ void Component::paintComponentAndChildren (Graphics& g) if (child->flags.dontClipGraphicsFlag) { - g.setOrigin (child->getX(), child->getY()); - child->paintEntireComponent (g, false); + child->paintTransformedChild (g); } else { @@ -1787,10 +1850,7 @@ void Component::paintComponentAndChildren (Graphics& g) } if (nothingClipped || ! g.isClipEmpty()) - { - g.setOrigin (child->getX(), child->getY()); - child->paintEntireComponent (g, false); - } + child->paintTransformedChild (g); } } @@ -2161,7 +2221,7 @@ void Component::addMouseListener (MouseListener* const newListener, { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED // If you register a component as a mouselistener for itself, it'll receive all the events // twice - once via the direct callback that all components get anyway, and then again as a listener! @@ -2177,7 +2237,7 @@ void Component::removeMouseListener (MouseListener* const listenerToRemove) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (mouseListeners_ != 0) mouseListeners_->removeListener (listenerToRemove); @@ -2437,7 +2497,7 @@ void Component::internalMouseDrag (MouseInputSource& source, const Point& r { Desktop& desktop = Desktop::getInstance(); - flags.mouseOverFlag = reallyContains (relativePos.getX(), relativePos.getY(), false); + flags.mouseOverFlag = reallyContains (relativePos, false); BailOutChecker checker (this); @@ -2807,7 +2867,7 @@ void Component::grabKeyboardFocus() { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED grabFocusInternal (focusChangedDirectly); } @@ -2816,7 +2876,7 @@ void Component::moveKeyboardFocusToSibling (const bool moveToNext) { // if component methods are being called from threads other than the message // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. - checkMessageManagerIsLocked + CHECK_MESSAGE_MANAGER_IS_LOCKED if (parentComponent_ != 0) { @@ -2872,20 +2932,9 @@ void Component::giveAwayFocus() } //============================================================================== -bool Component::isMouseOver() const throw() -{ - return flags.mouseOverFlag; -} - -bool Component::isMouseButtonDown() const throw() -{ - return flags.mouseDownFlag; -} - -bool Component::isMouseOverOrDragging() const throw() -{ - return flags.mouseOverFlag || flags.mouseDownFlag; -} +bool Component::isMouseOver() const throw() { return flags.mouseOverFlag; } +bool Component::isMouseButtonDown() const throw() { return flags.mouseDownFlag; } +bool Component::isMouseOverOrDragging() const throw() { return flags.mouseOverFlag || flags.mouseDownFlag; } bool JUCE_CALLTYPE Component::isMouseButtonDownAnywhere() throw() { @@ -2900,8 +2949,7 @@ const Point Component::getMouseXYRelative() const //============================================================================== const Rectangle Component::getParentMonitorArea() const { - return Desktop::getInstance() - .getMonitorAreaContaining (localPointToGlobal (getLocalBounds().getCentre())); + return Desktop::getInstance().getMonitorAreaContaining (getScreenBounds().getCentre()); } //============================================================================== diff --git a/src/gui/components/juce_Component.h b/src/gui/components/juce_Component.h index 948cbcb025..f0c69f1998 100644 --- a/src/gui/components/juce_Component.h +++ b/src/gui/components/juce_Component.h @@ -258,15 +258,21 @@ public: bool isAlwaysOnTop() const throw(); //============================================================================== - /** Returns the x co-ordinate of the component's left edge. + /** Returns the x coordinate of the component's left edge. This is a distance in pixels from the left edge of the component's parent. - @see getScreenX + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ inline int getX() const throw() { return bounds_.getX(); } - /** Returns the y co-ordinate of the top of this component. + /** Returns the y coordinate of the top of this component. This is a distance in pixels from the top edge of the component's parent. - @see getScreenY + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ inline int getY() const throw() { return bounds_.getY(); } @@ -276,26 +282,38 @@ public: /** Returns the component's height in pixels. */ inline int getHeight() const throw() { return bounds_.getHeight(); } - /** Returns the x co-ordinate of the component's right-hand edge. + /** Returns the x coordinate of the component's right-hand edge. This is a distance in pixels from the left edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ int getRight() const throw() { return bounds_.getRight(); } /** Returns the component's top-left position as a Point. */ const Point getPosition() const throw() { return bounds_.getPosition(); } - /** Returns the y co-ordinate of the bottom edge of this component. + /** Returns the y coordinate of the bottom edge of this component. This is a distance in pixels from the top edge of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ int getBottom() const throw() { return bounds_.getBottom(); } /** Returns this component's bounding box. The rectangle returned is relative to the top-left of the component's parent. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to its bounding box. */ const Rectangle& getBounds() const throw() { return bounds_; } /** Returns the component's bounds, relative to its own origin. - This is like getBounds(), but returns the rectangle in local co-ordinates, In practice, it'll + This is like getBounds(), but returns the rectangle in local coordinates, In practice, it'll return a rectangle with position (0, 0), and the same size as this component. */ const Rectangle getLocalBounds() const throw(); @@ -312,12 +330,12 @@ public: bool includeSiblings) const; //============================================================================== - /** Returns this component's x co-ordinate relative the the screen's top-left origin. + /** Returns this component's x coordinate relative the the screen's top-left origin. @see getX, localPointToGlobal */ int getScreenX() const; - /** Returns this component's y co-ordinate relative the the screen's top-left origin. + /** Returns this component's y coordinate relative the the screen's top-left origin. @see getY, localPointToGlobal */ int getScreenY() const; @@ -346,6 +364,10 @@ public: This takes a rectangle that is relative to a different component, and returns its position relative to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. */ const Rectangle getLocalArea (const Component* sourceComponent, const Rectangle& areaRelativeToSourceComponent) const; @@ -356,6 +378,10 @@ public: const Point localPointToGlobal (const Point& localPoint) const; /** Converts a rectangle from this component's coordinate space to a screen coordinate. + + If you've used setTransform() to apply one or more transforms to components, then the source rectangle + may not actually be rectanglular when converted to the target space, so in that situation this will return + the smallest rectangle that fully contains the transformed area. @see getLocalPoint, localPointToGlobal */ const Rectangle localAreaToGlobal (const Rectangle& localArea) const; @@ -368,6 +394,10 @@ public: If the component actually moves, this method will make a synchronous call to moved(). + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setBounds, ComponentListener::componentMovedOrResized */ void setTopLeftPosition (int x, int y); @@ -378,29 +408,51 @@ public: The position is relative to the top-left of the component's parent. If the component actually moves, this method will make a synchronous call to moved(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. */ void setTopRightPosition (int x, int y); /** Changes the size of the component. A synchronous call to resized() will be occur if the size actually changes. + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. */ void setSize (int newWidth, int newHeight); /** Changes the component's position and size. - The co-ordinates are relative to the top-left of the component's parent, or relative + The coordinates are relative to the top-left of the component's parent, or relative to the origin of the screen is the component is on the desktop. If this method changes the component's top-left position, it will make a synchronous call to moved(). If it changes the size, it will also make a call to resized(). + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setTopLeftPosition, setSize, ComponentListener::componentMovedOrResized */ void setBounds (int x, int y, int width, int height); /** Changes the component's position and size. + The coordinates are relative to the top-left of the component's parent, or relative + to the origin of the screen is the component is on the desktop. + + If this method changes the component's top-left position, it will make a synchronous + call to moved(). If it changes the size, it will also make a call to resized(). + + Note that if you've used setTransform() to apply a transform, then the component's + bounds will no longer be a direct reflection of the position at which it appears within + its parent, as the transform will be applied to whatever bounds you set for it. + @see setBounds */ void setBounds (const Rectangle& newBounds); @@ -411,6 +463,8 @@ public: setBoundsRelative (0.2f, 0.2f, 0.5f, 0.5f) would give it half the width and height of the parent, with its top-left position 20% of the way across and down the parent. + + @see setBounds */ void setBoundsRelative (float proportionalX, float proportionalY, float proportionalWidth, float proportionalHeight); @@ -419,6 +473,8 @@ public: This will position the component within its parent, leaving the specified number of pixels around each edge. + + @see setBounds */ void setBoundsInset (const BorderSize& borders); @@ -433,6 +489,8 @@ public: It will then be positioned within the rectangle according to the justification flags specified. + + @see setBounds */ void setBoundsToFit (int x, int y, int width, int height, const Justification& justification, @@ -442,6 +500,8 @@ public: Leaves the component's size unchanged, but sets the position of its centre relative to its parent's top-left. + + @see setBounds */ void setCentrePosition (int x, int y); @@ -461,6 +521,38 @@ public: */ void centreWithSize (int width, int height); + //============================================================================== + /** Sets a transform matrix to be applied to this component. + + If you set a transform for a component, the component's position will be warped by it, relative to + the component's parent's top-left origin. This means that the values you pass into setBounds() will no + longer reflect the actual area within the parent that the component covers, as the bounds will be + transformed and the component will probably end up actually appearing somewhere else within its parent. + + When using transforms you need to be extremely careful when converting coordinates between the + coordinate spaces of different components or the screen - you should always use getLocalPoint(), + getLocalArea(), etc to do this, and never just manually add a component's position to a point in order to + convert it between different components (but I'm sure you would never have done that anyway...). + + Currently, transforms are not supported for desktop windows, so the transform will be ignored if you + put a component on the desktop. + + To remove a component's transform, simply pass AffineTransform::identity as the parameter to this method. + */ + void setTransform (const AffineTransform& transform); + + /** Returns the transform that is currently being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + const AffineTransform getTransform() const; + + /** Returns true if a non-identity transform is being applied to this component. + For more details about transforms, see setTransform(). + @see setTransform + */ + bool isTransformed() const throw(); + //============================================================================== /** Returns a proportion of the component's width. @@ -488,7 +580,7 @@ public: */ int getParentHeight() const throw(); - /** Returns the screen co-ordinates of the monitor that contains this component. + /** Returns the screen coordinates of the monitor that contains this component. If there's only one monitor, this will return its size - if there are multiple monitors, it will return the area of the monitor that contains the component's @@ -660,7 +752,7 @@ public: Overriding this method allows you to create components which only intercept mouse-clicks within a user-defined area. - This is called to find out whether a particular x, y co-ordinate is + This is called to find out whether a particular x, y coordinate is considered to be inside the component or not, and is used by methods such as contains() and getComponentAt() to work out which component the mouse is clicked on. @@ -682,10 +774,10 @@ public: Note that for components on the desktop, this method will be ignored, because it's not always possible to implement this behaviour on all platforms. - @param x the x co-ordinate to test, relative to the left hand edge of this + @param x the x coordinate to test, relative to the left hand edge of this component. This value is guaranteed to be greater than or equal to zero, and less than the component's width - @param y the y co-ordinate to test, relative to the top edge of this + @param y the y coordinate to test, relative to the top edge of this component. This value is guaranteed to be greater than or equal to zero, and less than the component's height @returns true if the click is considered to be inside the component @@ -728,32 +820,29 @@ public: Never override this method! Use hitTest to create custom hit regions. - @param point the x co-ordinate to test, relative to this component's top-left. + @param localPoint the coordinate to test, relative to this component's top-left. @returns true if the point is within the component's hit-test area, but only if that part of the component isn't clipped by its parent component. Note that this won't take into account any overlapping sibling components which might be in the way - for that, see reallyContains() @see hitTest, reallyContains, getComponentAt */ - bool contains (const Point& point); + bool contains (const Point& localPoint); /** Returns true if a given point lies in this component, taking any overlapping siblings into account. - @param x the x co-ordinate to test, relative to this component's left hand edge. - @param y the y co-ordinate to test, relative to this component's top edge. - @param returnTrueIfWithinAChild if the point actually lies within a child of this - component, this determines the value that will - be returned. - + @param localPoint the coordinate to test, relative to this component's top-left. + @param returnTrueIfWithinAChild if the point actually lies within a child of this component, + this determines whether that is counted as a hit. @see contains, getComponentAt */ - bool reallyContains (int x, int y, bool returnTrueIfWithinAChild); + bool reallyContains (const Point& localPoint, bool returnTrueIfWithinAChild); /** Returns the component at a certain point within this one. - @param x the x co-ordinate to test, relative to this component's left hand edge. - @param y the y co-ordinate to test, relative to this component's top edge. + @param x the x coordinate to test, relative to this component's left edge. + @param y the y coordinate to test, relative to this component's top edge. @returns the component that is at this position - which may be 0, this component, or one of its children. Note that overlapping siblings that might actually be in the way are not taken into account by this method - to account for these, @@ -764,7 +853,7 @@ public: /** Returns the component at a certain point within this one. - @param position the co-ordinates to test, relative to this component's top-left. + @param position the coordinate to test, relative to this component's top-left. @returns the component that is at this position - which may be 0, this component, or one of its children. Note that overlapping siblings that might actually be in the way are not taken into account by this method - to account for these, @@ -1613,8 +1702,7 @@ public: static bool JUCE_CALLTYPE isMouseButtonDownAnywhere() throw(); /** Returns the mouse's current position, relative to this component. - - The co-ordinates are relative to the component's top-left corner. + The return value is relative to the component's top-left corner. */ const Point getMouseXYRelative() const; @@ -2033,6 +2121,7 @@ private: String componentName_; Component* parentComponent_; Rectangle bounds_; + ScopedPointer affineTransform_; Array childComponentList_; LookAndFeel* lookAndFeel_; MouseCursor cursor_; @@ -2099,6 +2188,7 @@ private: void internalHierarchyChanged(); void paintComponentAndChildren (Graphics& g); void paintComponent (Graphics& g); + void paintTransformedChild (Graphics& g); void sendMovedResizedMessages (bool wasMoved, bool wasResized); void repaintParent(); void sendFakeMouseMove() const; diff --git a/src/gui/components/menus/juce_MenuBarComponent.cpp b/src/gui/components/menus/juce_MenuBarComponent.cpp index 9e56698cee..d9bd35552c 100644 --- a/src/gui/components/menus/juce_MenuBarComponent.cpp +++ b/src/gui/components/menus/juce_MenuBarComponent.cpp @@ -127,7 +127,7 @@ int MenuBarComponent::getItemAt (const int x, const int y) { for (int i = 0; i < xPositions.size(); ++i) if (x >= xPositions[i] && x < xPositions[i + 1]) - return reallyContains (x, y, true) ? i : -1; + return reallyContains (Point (x, y), true) ? i : -1; return -1; } diff --git a/src/gui/components/menus/juce_PopupMenu.cpp b/src/gui/components/menus/juce_PopupMenu.cpp index 7b4086ee8b..fdea599194 100644 --- a/src/gui/components/menus/juce_PopupMenu.cpp +++ b/src/gui/components/menus/juce_PopupMenu.cpp @@ -522,7 +522,7 @@ public: // comp that we're attached to. const Point mousePos (componentAttachedTo->getMouseXYRelative()); - if (componentAttachedTo->reallyContains (mousePos.getX(), mousePos.getY(), true)) + if (componentAttachedTo->reallyContains (mousePos, true)) { postCommandMessage (PopupMenuSettings::dismissCommandId); // dismiss asynchrounously return; @@ -567,7 +567,7 @@ public: const uint32 now = Time::getMillisecondCounter(); if (now > timeEnteredCurrentChildComp + 100 - && reallyContains (localMousePos.getX(), localMousePos.getY(), true) + && reallyContains (localMousePos, true) && currentChild != 0 && (! disableMouseMoves) && ! (activeSubMenu != 0 && activeSubMenu->isVisible())) @@ -659,7 +659,7 @@ public: else if (wasDown && now > menuCreationTime + 250 && ! (isDown || overScrollArea)) { - isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); + isOver = reallyContains (localMousePos, true); if (isOver) { @@ -734,7 +734,7 @@ private: void updateMouseOverStatus (const Point& globalMousePos) { const Point relPos (getLocalPoint (0, globalMousePos)); - isOver = reallyContains (relPos.getX(), relPos.getY(), true); + isOver = reallyContains (relPos, true); if (activeSubMenu != 0) activeSubMenu->updateMouseOverStatus (globalMousePos); @@ -1081,7 +1081,7 @@ private: void highlightItemUnderMouse (const Point& globalMousePos, const Point& localMousePos) { - isOver = reallyContains (localMousePos.getX(), localMousePos.getY(), true); + isOver = reallyContains (localMousePos, true); if (isOver) hasBeenOver = true; diff --git a/src/gui/components/mouse/juce_MouseHoverDetector.cpp b/src/gui/components/mouse/juce_MouseHoverDetector.cpp index 4df80ef9ef..88e1221580 100644 --- a/src/gui/components/mouse/juce_MouseHoverDetector.cpp +++ b/src/gui/components/mouse/juce_MouseHoverDetector.cpp @@ -76,7 +76,7 @@ void MouseHoverDetector::hoverTimerCallback() { const Point pos (source->getMouseXYRelative()); - if (source->reallyContains (pos.getX(), pos.getY(), false)) + if (source->reallyContains (pos, false)) { hasJustHovered = true; mouseHovered (pos.getX(), pos.getY()); diff --git a/src/gui/components/special/juce_MidiKeyboardComponent.cpp b/src/gui/components/special/juce_MidiKeyboardComponent.cpp index 8020fb2cd2..7c2ba8679b 100644 --- a/src/gui/components/special/juce_MidiKeyboardComponent.cpp +++ b/src/gui/components/special/juce_MidiKeyboardComponent.cpp @@ -244,7 +244,7 @@ const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 }; int MidiKeyboardComponent::xyToNote (const Point& pos, float& mousePositionVelocity) { - if (! reallyContains (pos.getX(), pos.getY(), false)) + if (! reallyContains (pos, false)) return -1; Point p (pos);