diff --git a/modules/juce_graphics/juce_graphics.h b/modules/juce_graphics/juce_graphics.h index 646ee71833..d219f470e8 100644 --- a/modules/juce_graphics/juce_graphics.h +++ b/modules/juce_graphics/juce_graphics.h @@ -22,7 +22,7 @@ ============================================================================== */ -#ifndef __JUCE_GRAPHICS_MODULE_JUCEHEADER__ +#ifndef __JUCE_GRAPHICS_MODULE_JUCEHEADER__ // %% #define __JUCE_GRAPHICS_MODULE_JUCEHEADER__ #include "../juce_core/juce_core.h" @@ -172,4 +172,4 @@ namespace juce } -#endif // __JUCE_GRAPHICS_JUCEHEADER__ +#endif // __JUCE_GRAPHICS_MODULE_JUCEHEADER__ diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index e583c72138..31b430cd26 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -147,6 +147,7 @@ namespace juce #include "mouse/juce_DragAndDropContainer.cpp" #include "mouse/juce_MouseCursor.cpp" #include "mouse/juce_MouseEvent.cpp" +#include "mouse/juce_MouseInactivityDetector.cpp" #include "mouse/juce_MouseInputSource.cpp" #include "mouse/juce_MouseListener.cpp" #include "keyboard/juce_CaretComponent.cpp" diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index cc9d698ade..6b5157630f 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -111,6 +111,9 @@ namespace juce #ifndef __JUCE_MOUSEEVENT_JUCEHEADER__ #include "mouse/juce_MouseEvent.h" #endif +#ifndef __JUCE_MOUSEINACTIVITYDETECTOR_JUCEHEADER__ + #include "mouse/juce_MouseInactivityDetector.h" +#endif #ifndef __JUCE_MOUSEINPUTSOURCE_JUCEHEADER__ #include "mouse/juce_MouseInputSource.h" #endif @@ -240,6 +243,12 @@ namespace juce #ifndef __JUCE_WILDCARDFILEFILTER_JUCEHEADER__ #include "filebrowser/juce_WildcardFileFilter.h" #endif +#ifndef __JUCE_ANIMATEDPOSITION_JUCEHEADER__ + #include "layout/juce_AnimatedPosition.h" +#endif +#ifndef __JUCE_ANIMATEDPOSITIONBEHAVIOURS_JUCEHEADER__ + #include "layout/juce_AnimatedPositionBehaviours.h" +#endif #ifndef __JUCE_COMPONENTANIMATOR_JUCEHEADER__ #include "layout/juce_ComponentAnimator.h" #endif diff --git a/modules/juce_gui_basics/layout/juce_AnimatedPosition.h b/modules/juce_gui_basics/layout/juce_AnimatedPosition.h new file mode 100644 index 0000000000..2dcfeb34ac --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_AnimatedPosition.h @@ -0,0 +1,208 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_ANIMATEDPOSITION_JUCEHEADER__ +#define __JUCE_ANIMATEDPOSITION_JUCEHEADER__ + +//============================================================================== +/** + Models a 1-dimensional position that can be dragged around by the user, and which + will then continue moving with a customisable physics behaviour when released. + + This is useful for things like scrollable views or objects that can be dragged and + thrown around with the mouse/touch, and by writing your own behaviour class, you can + customise the trajectory that it follows when released. + + The class uses its own Timer to continuously change its value when a drag ends, and + Listener objects can be registered to receive callbacks whenever the value changes. + + The value is stored as a double, and can be used to represent whatever units you need. + + The template parameter Behaviour must be a class that implements various methods to + return the physics of the value's movement - you can use the classes provided for this + in the AnimatedPositionBehaviours namespace, or write your own custom behaviour. + + @see AnimatedPositionBehaviours::ContinuousWithMomentum, + AnimatedPositionBehaviours::SnapToPageBoundaries +*/ +template +class AnimatedPosition : private Timer +{ +public: + AnimatedPosition() + : position(), grabbedPos(), releaseVelocity(), + range (-std::numeric_limits::max(), + std::numeric_limits::max()) + { + } + + /** Sets a range within which the value will be constrained. */ + void setLimits (Range newRange) + { + range = newRange; + } + + //============================================================================== + /** Called to indicate that the object is now being controlled by a + mouse-drag or similar operation. + + After calling this method, you should make calls to the drag() method + each time the mouse drags the position around, and always be sure to + finish with a call to endDrag() when the mouse is released, which allows + the position to continue moving freely according to the specified behaviour. + */ + void beginDrag() + { + grabbedPos = position; + releaseVelocity = 0; + stopTimer(); + } + + /** Called during a mouse-drag operation, to indicate that the mouse has moved. + The delta is the difference between the position when beginDrag() was called + and the new position that's required. + */ + void drag (double deltaFromStartOfDrag) + { + moveTo (grabbedPos + deltaFromStartOfDrag); + } + + /** Called after beginDrag() and drag() to indicate that the drag operation has + now finished. + */ + void endDrag() + { + startTimer (1000 / 60); + } + + /** Called outside of a drag operation to cause a nudge in the specified direction. + This is intended for use by e.g. mouse-wheel events. + */ + void nudge (double deltaFromCurrentPosition) + { + startTimer (100); + moveTo (position + deltaFromCurrentPosition); + } + + //============================================================================== + /** Returns the current position. */ + double getPosition() const noexcept + { + return position; + } + + /** Explicitly sets the position and stops any further movement. + This will cause a synchronous call to any listeners if the position actually + changes. + */ + void setPosition (double newPosition) + { + stopTimer(); + setPositionAndSendChange (newPosition); + } + + //============================================================================== + /** Implement this class if you need to receive callbacks when the value of + an AnimatedPosition changes. + @see AnimatedPosition::addListener, AnimatedPosition::removeListener + */ + class Listener + { + public: + virtual ~Listener() {} + + /** Called synchronously when an AnimatedPosition changes. */ + virtual void positionChanged (AnimatedPosition&, double newPosition) = 0; + }; + + /** Adds a listener to be called when the value changes. */ + void addListener (Listener* listener) { listeners.add (listener); } + + /** Removes a previously-registered listener. */ + void removeListener (Listener* listener) { listeners.remove (listener); } + + //============================================================================== + /** The behaviour object. + This is public to let you tweak any parameters that it provides. + */ + Behaviour behaviour; + +private: + //============================================================================== + double position, grabbedPos, releaseVelocity; + Range range; + Time lastUpdate, lastDrag; + ListenerList listeners; + + static double getSpeed (const Time last, double lastPos, + const Time now, double newPos) + { + const double elapsedSecs = jmax (0.005, (now - last).inSeconds()); + const double v = (newPos - lastPos) / elapsedSecs; + return std::abs (v) > 0.2 ? v : 0.0; + } + + void moveTo (double newPos) + { + const Time now (Time::getCurrentTime()); + releaseVelocity = getSpeed (lastDrag, position, now, newPos); + behaviour.releasedWithVelocity (newPos, releaseVelocity); + lastDrag = now; + + setPositionAndSendChange (newPos); + } + + void setPositionAndSendChange (double newPosition) + { + newPosition = range.clipValue (newPosition); + + if (position != newPosition) + { + position = newPosition; + listeners.call (&Listener::positionChanged, *this, newPosition); + } + } + + void timerCallback() override + { + const Time now = Time::getCurrentTime(); + + const double elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds()); + lastUpdate = now; + + const double newPos = behaviour.getNextPosition (position, elapsed); + + if (behaviour.isStopped (newPos)) + stopTimer(); + else + startTimer (1000 / 60); + + setPositionAndSendChange (newPos); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition) +}; + + +#endif // __JUCE_ANIMATEDPOSITION_JUCEHEADER__ diff --git a/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h b/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h new file mode 100644 index 0000000000..41309437c7 --- /dev/null +++ b/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_ANIMATEDPOSITIONBEHAVIOURS_JUCEHEADER__ +#define __JUCE_ANIMATEDPOSITIONBEHAVIOURS_JUCEHEADER__ + + +//============================================================================== +/** Contains classes for different types of physics behaviours - these classes + are used as template parameters for the AnimatedPosition class. +*/ +namespace AnimatedPositionBehaviours +{ + /** A non-snapping behaviour that allows the content to be freely flicked in + either direction, with momentum based on the velocity at which it was + released, and variable friction to make it come to a halt. + + This class is intended to be used as a template parameter to the + AnimatedPosition class. + + @see AnimatedPosition + */ + struct ContinuousWithMomentum + { + ContinuousWithMomentum() noexcept + : velocity (0), damping (0.92) + { + } + + /** Sets the friction that damps the movement of the value. + A typical value is 0.08; higher values indicate more friction. + */ + void setFriction (double newFriction) noexcept + { + damping = 1.0 - newFriction; + } + + /** Called by the AnimatedPosition class. This tells us the position and + velocity at which the user is about to release the object. + The velocity is measured in units/second. + */ + void releasedWithVelocity (double /*position*/, double releaseVelocity) noexcept + { + velocity = releaseVelocity; + } + + /** Called by the AnimatedPosition class to get the new position, after + the given time has elapsed. + */ + double getNextPosition (double oldPos, double elapsedSeconds) noexcept + { + velocity *= damping; + + if (std::abs (velocity) < 0.05) + velocity = 0; + + return oldPos + velocity * elapsedSeconds; + } + + /** Called by the AnimatedPosition class to check whether the object + is now stationary. + */ + bool isStopped (double /*position*/) const noexcept + { + return velocity == 0; + } + + private: + double velocity, damping; + }; + + //============================================================================== + /** A behaviour that gravitates an AnimatedPosition object towards the nearest + integer position when released. + + This class is intended to be used as a template parameter to the + AnimatedPosition class. It's handy when using an AnimatedPosition to show a + series of pages, because it allows the pages can be scrolled smoothly, but when + released, snaps back to show a whole page. + + @see AnimatedPosition + */ + struct SnapToPageBoundaries + { + SnapToPageBoundaries() noexcept : targetSnapPosition() + { + } + + /** Called by the AnimatedPosition class. This tells us the position and + velocity at which the user is about to release the object. + The velocity is measured in units/second. + */ + void releasedWithVelocity (double position, double releaseVelocity) noexcept + { + targetSnapPosition = std::floor (position + 0.5); + + if (releaseVelocity > 1.0 && targetSnapPosition < position) ++targetSnapPosition; + if (releaseVelocity < -1.0 && targetSnapPosition > position) --targetSnapPosition; + } + + /** Called by the AnimatedPosition class to get the new position, after + the given time has elapsed. + */ + double getNextPosition (double oldPos, double elapsedSeconds) const noexcept + { + if (isStopped (oldPos)) + return targetSnapPosition; + + const double snapSpeed = 10.0; + const double velocity = (targetSnapPosition - oldPos) * snapSpeed; + return oldPos + velocity * elapsedSeconds; + } + + /** Called by the AnimatedPosition class to check whether the object + is now stationary. + */ + bool isStopped (double position) const noexcept + { + return std::abs (targetSnapPosition - position) < 0.001; + } + + private: + double targetSnapPosition; + }; +}; + + +#endif // __JUCE_ANIMATEDPOSITIONBEHAVIOURS_JUCEHEADER__ diff --git a/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.cpp b/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.cpp new file mode 100644 index 0000000000..fc5bc087a2 --- /dev/null +++ b/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.cpp @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +MouseInactivityDetector::MouseInactivityDetector (Component& c) + : targetComp (c), delayMs (1500), isActive (true) +{ + targetComp.addMouseListener (this, true); +} + +MouseInactivityDetector::~MouseInactivityDetector() +{ + targetComp.removeMouseListener (this); +} + +void MouseInactivityDetector::setDelay (int newDelayMilliseconds) +{ + delayMs = newDelayMilliseconds; +} + + +void MouseInactivityDetector::addListener (Listener* l) { listenerList.add (l); } +void MouseInactivityDetector::removeListener (Listener* l) { listenerList.remove (l); } + +void MouseInactivityDetector::timerCallback() +{ + setActive (false); +} + +void MouseInactivityDetector::wakeUp (const MouseEvent& e, bool alwaysWake) +{ + const Point newPos (e.getEventRelativeTo (&targetComp).getPosition()); + + if ((! isActive) && (alwaysWake || e.source.isTouch() || newPos.getDistanceFrom (lastMousePos) > 15)) + setActive (true); + + lastMousePos = newPos; + startTimer (delayMs); +} + +void MouseInactivityDetector::setActive (bool b) +{ + if (isActive != b) + { + isActive = b; + + listenerList.call (b ? &Listener::mouseBecameActive + : &Listener::mouseBecameInactive); + } +} diff --git a/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.h b/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.h new file mode 100644 index 0000000000..fec6ad8a1c --- /dev/null +++ b/modules/juce_gui_basics/mouse/juce_MouseInactivityDetector.h @@ -0,0 +1,105 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission is granted to use this software under the terms of either: + a) the GPL v2 (or any later version) + b) the Affero GPL v3 + + Details of these licenses can be found at: www.gnu.org/licenses + + JUCE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.juce.com for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_MOUSEINACTIVITYDETECTOR_JUCEHEADER__ +#define __JUCE_MOUSEINACTIVITYDETECTOR_JUCEHEADER__ + +//============================================================================== +/** + This object watches for mouse-events happening within a component, and if + the mouse remains still for long enough, triggers an event to indicate that + it has become inactive. + + You'd use this for situations where e.g. you want to hide the mouse-cursor + when the user's not actively using the mouse. + + After creating an instance of this, use addListener to get callbacks when + the activity status changes. +*/ +class JUCE_API MouseInactivityDetector : private Timer, + private MouseListener +{ +public: + /** Creates an inactivity watcher, attached to the given component. + The target component must not be deleted while this - it will be monitored + for any mouse events in it or its child components. + */ + MouseInactivityDetector (Component& target); + + /** Destructor. */ + ~MouseInactivityDetector(); + + /** Sets the time for which the mouse must be still before the callback + is triggered. + */ + void setDelay (int newDelayMilliseconds); + + //============================================================================== + /** Classes should implement this to receive callbacks from a MouseInactivityDetector + when the mouse becomes active or inactive. + */ + class Listener + { + public: + virtual ~Listener() {} + + /** Called when the mouse is moved or clicked for the first time + after a period of inactivity. */ + virtual void mouseBecameActive() = 0; + + /** Called when the mouse hasn't been moved for the timeout period. */ + virtual void mouseBecameInactive() = 0; + }; + + /** Registers a listener. */ + void addListener (Listener* listener); + + /** Removes a previously-registered listener. */ + void removeListener (Listener* listener); + +private: + //============================================================================== + Component& targetComp; + ListenerList listenerList; + Point lastMousePos; + int delayMs; + bool isActive; + + void timerCallback() override; + void wakeUp (const MouseEvent&, bool alwaysWake); + void setActive (bool); + + void mouseMove (const MouseEvent& e) override { wakeUp (e, false); } + void mouseEnter (const MouseEvent& e) override { wakeUp (e, false); } + void mouseExit (const MouseEvent& e) override { wakeUp (e, false); } + void mouseDown (const MouseEvent& e) override { wakeUp (e, true); } + void mouseDrag (const MouseEvent& e) override { wakeUp (e, true); } + void mouseUp (const MouseEvent& e) override { wakeUp (e, true); } + void mouseWheelMove (const MouseEvent& e, const MouseWheelDetails&) override { wakeUp (e, true); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInactivityDetector) +}; + + +#endif // __JUCE_MOUSEINACTIVITYDETECTOR_JUCEHEADER__