#include <dali/public-api/events/mouse-wheel-event.h>
#include <dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h>
#include <dali-toolkit/internal/controls/scrollable/scroll-connector-impl.h>
-#include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
+#include <dali-toolkit/internal/controls/scrollable/bouncing-effect-actor.h>
using namespace std;
using namespace Dali;
const float DEFAULT_MINIMUM_SWIPE_DISTANCE = 3.0f;
const float DEFAULT_MOUSE_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION = 0.1f;
+const float DEFAULT_MINIMUM_SWIPE_DURATION = 0.45f;
+const float DEFAULT_MAXIMUM_SWIPE_DURATION = 2.6f;
+
+
const float DEFAULT_REFRESH_INTERVAL_LAYOUT_POSITIONS = 20.0f; // 1 updates per 20 items
const int MOUSE_WHEEL_EVENT_FINISHED_TIME_OUT = 500; // 0.5 second
const float MILLISECONDS_PER_SECONDS = 1000.0f;
-const Rect<int> OVERSHOOT_BOUNCE_IMAGE_1_PIXEL_AREA( 0, 0, 720, 58 );
+const Vector2 OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE( 720.0f, 42.0f );
+const float OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD = 180.0f;
const Vector4 OVERSHOOT_OVERLAY_NINE_PATCH_BORDER(0.0f, 0.0f, 1.0f, 12.0f);
const float MAXIMUM_OVERSHOOT_HEIGHT = 36.0f; // 36 pixels
const float DEFAULT_OVERSHOOT_ANIMATION_DURATION = 0.5f; // 0.5 second
struct OvershootOverlaySizeConstraint
{
- float operator()(const float& current,
+ Vector3 operator()(const Vector3& current,
const PropertyInput& parentScrollDirectionProperty,
const PropertyInput& parentOvershootProperty,
const PropertyInput& parentSizeProperty)
overlayWidth = fabsf(parentScrollDirection.x) > Math::MACHINE_EPSILON_1 ? parentSize.y : parentSize.x;
}
- return overlayWidth;
+ float overlayHeight = (overlayWidth > OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD) ? OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height : OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height*0.5f;
+
+ return Vector3( overlayWidth, overlayHeight, current.depth );
}
};
mAnimateOvershootOff(false),
mAnchoringEnabled(true),
mAnchoringDuration(DEFAULT_ANCHORING_DURATION),
- mRefreshIntervalLayoutPositions(DEFAULT_REFRESH_INTERVAL_LAYOUT_POSITIONS),
+ mRefreshIntervalLayoutPositions(0.0f),
mRefreshOrderHint(true/*Refresh item 0 first*/),
mMinimumSwipeSpeed(DEFAULT_MINIMUM_SWIPE_SPEED),
mMinimumSwipeDistance(DEFAULT_MINIMUM_SWIPE_DISTANCE),
mScrollConnector = Dali::Toolkit::ScrollConnector::New();
mScrollPositionObject = mScrollConnector.GetScrollPositionObject();
+ mScrollConnector.ScrollPositionChangedSignal().Connect( this, &ItemView::OnScrollPositionChanged );
mPropertyMinimumLayoutPosition = self.RegisterProperty(MINIMUM_LAYOUT_POSITION_PROPERTY_NAME, 0.0f);
mPropertyPosition = self.RegisterProperty(POSITION_PROPERTY_NAME, 0.0f);
mMouseWheelEventFinishedTimer = Timer::New( MOUSE_WHEEL_EVENT_FINISHED_TIME_OUT );
mMouseWheelEventFinishedTimer.TickSignal().Connect( this, &ItemView::OnMouseWheelEventFinished );
- SetRefreshInterval(mRefreshIntervalLayoutPositions);
+ SetRefreshInterval(DEFAULT_REFRESH_INTERVAL_LAYOUT_POSITIONS);
}
ItemView::~ItemView()
float ItemView::GetCurrentLayoutPosition(unsigned int itemId) const
{
- return mScrollPositionObject.GetProperty<float>( ScrollConnector::SCROLL_POSITION ) + static_cast<float>( itemId );
+ return mScrollConnector.GetScrollPosition() + static_cast<float>( itemId );
}
void ItemView::ActivateLayout(unsigned int layoutIndex, const Vector3& targetSize, float durationSeconds)
{
DALI_ASSERT_ALWAYS(layoutIndex < mLayouts.size());
+ mRefreshEnabled = false;
+
Actor self = Self();
// The ItemView size should match the active layout size
}
// Refresh the new layout
- ItemRange range = GetItemRange(*mActiveLayout, targetSize, GetCurrentLayoutPosition(0), true/*reserve extra*/);
+ ItemRange range = GetItemRange(*mActiveLayout, targetSize, GetCurrentLayoutPosition(0), false/* don't reserve extra*/);
AddActorsWithinRange( range, durationSeconds );
// Scroll to an appropriate layout position
if (scrollAnimationNeeded)
{
RemoveAnimation(mScrollAnimation);
- mScrollAnimation = Animation::New(mAnchoringDuration);
+ mScrollAnimation = Animation::New(durationSeconds);
mScrollAnimation.AnimateTo( Property( mScrollPositionObject, ScrollConnector::SCROLL_POSITION ), firstItemScrollPosition, AlphaFunctions::EaseOut );
mScrollAnimation.AnimateTo( Property(self, mPropertyPosition), GetScrollPosition(firstItemScrollPosition, targetSize), AlphaFunctions::EaseOut );
+ mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnLayoutActivationScrollFinished);
mScrollAnimation.Play();
}
void ItemView::OnRefreshNotification(PropertyNotification& source)
{
- if(mRefreshEnabled)
+ if(mRefreshEnabled || mScrollAnimation)
{
// Only refresh the cache during normal scrolling
DoRefresh(GetCurrentLayoutPosition(0), true);
void ItemView::SetRefreshInterval(float intervalLayoutPositions)
{
- mRefreshIntervalLayoutPositions = intervalLayoutPositions;
-
- if(mRefreshNotification)
+ if(mRefreshIntervalLayoutPositions != intervalLayoutPositions)
{
- mScrollPositionObject.RemovePropertyNotification(mRefreshNotification);
+ mRefreshIntervalLayoutPositions = intervalLayoutPositions;
+
+ if(mRefreshNotification)
+ {
+ mScrollPositionObject.RemovePropertyNotification(mRefreshNotification);
+ }
+ mRefreshNotification = mScrollPositionObject.AddPropertyNotification( ScrollConnector::SCROLL_POSITION, StepCondition(mRefreshIntervalLayoutPositions, 0.0f) );
+ mRefreshNotification.NotifySignal().Connect( this, &ItemView::OnRefreshNotification );
}
- mRefreshNotification = mScrollPositionObject.AddPropertyNotification( ScrollConnector::SCROLL_POSITION, StepCondition(mRefreshIntervalLayoutPositions, 0.0f) );
- mRefreshNotification.NotifySignal().Connect( this, &ItemView::OnRefreshNotification );
}
float ItemView::GetRefreshInterval() const
if( removeIter != mItemPool.end() )
{
- Self().Remove( removeIter->second );
+ ReleaseActor(itemId, removeIter->second);
+
removed = true;
// Adjust the remaining item IDs, for example if item 2 is removed:
const ItemPoolIter iter = mItemPool.find( replacementItem.first );
if( mItemPool.end() != iter )
{
- Self().Remove( iter->second );
+ ReleaseActor(iter->first, iter->second);
iter->second = replacementItem.second;
}
else
if( ! range.Within( current ) )
{
- Self().Remove( iter->second );
+ ReleaseActor(iter->first, iter->second);
mItemPool.erase( iter++ ); // erase invalidates the return value of post-increment; iter remains valid
}
}
}
+void ItemView::ReleaseActor( ItemId item, Actor actor )
+{
+ Self().Remove( actor );
+ mItemFactory.ItemReleased(item, actor);
+}
+
ItemRange ItemView::GetItemRange(ItemLayout& layout, const Vector3& layoutSize, float layoutPosition, bool reserveExtra)
{
unsigned int itemCount = mItemFactory.GetNumberOfItems();
mScrollPositionObject.SetProperty( ScrollConnector::SCROLL_POSITION, firstItemScrollPosition );
self.SetProperty(mPropertyPosition, GetScrollPosition(firstItemScrollPosition, layoutSize));
mScrollStartedSignalV2.Emit(GetCurrentScrollPosition());
+ mRefreshEnabled = true;
}
if (mMouseWheelEventFinishedTimer.IsRunning())
RemoveAnimation(mScrollAnimation);
- float flickAnimationDuration = mActiveLayout->GetItemFlickAnimationDuration() * max(1.0f, fabsf(firstItemScrollPosition - GetCurrentLayoutPosition(0)));
+ float flickAnimationDuration = Clamp( mActiveLayout->GetItemFlickAnimationDuration() * max(1.0f, fabsf(firstItemScrollPosition - GetCurrentLayoutPosition(0)))
+ , DEFAULT_MINIMUM_SWIPE_DURATION, DEFAULT_MAXIMUM_SWIPE_DURATION);
+
mScrollAnimation = Animation::New(flickAnimationDuration);
mScrollAnimation.AnimateTo( Property( mScrollPositionObject, ScrollConnector::SCROLL_POSITION ), firstItemScrollPosition, AlphaFunctions::EaseOut );
mScrollAnimation.AnimateTo( Property(self, mPropertyPosition), GetScrollPosition(firstItemScrollPosition, layoutSize), AlphaFunctions::EaseOut );
case Gesture::Started: // Fall through
{
mTotalPanDisplacement = Vector2::ZERO;
+ mScrollStartedSignalV2.Emit(GetCurrentScrollPosition());
+ mRefreshEnabled = true;
}
case Gesture::Continuing:
{
mScrollDistance = CalculateScrollDistance(gesture.displacement, *mActiveLayout);
- mScrollSpeed = Clamp((gesture.GetSpeed() * mActiveLayout->GetScrollSpeedFactor() * MILLISECONDS_PER_SECONDS), 0.0f, mActiveLayout->GetMaximumSwipeSpeed());
+ mScrollSpeed = Clamp((gesture.GetSpeed() * gesture.GetSpeed() * mActiveLayout->GetFlickSpeedFactor() * MILLISECONDS_PER_SECONDS), 0.0f, mActiveLayout->GetMaximumSwipeSpeed());
// Refresh order depends on the direction of the scroll; negative is towards the last item.
mRefreshOrderHint = mScrollDistance < 0.0f;
mScrollPositionObject.SetProperty( ScrollConnector::SCROLL_POSITION, firstItemScrollPosition );
self.SetProperty(mPropertyPosition, GetScrollPosition(firstItemScrollPosition, layoutSize));
- mScrollStartedSignalV2.Emit(GetCurrentScrollPosition());
mTotalPanDisplacement += gesture.displacement;
mScrollOvershoot = layoutPositionDelta - firstItemScrollPosition;
mScrollOvershoot = 0.0f;
}
+void ItemView::OnLayoutActivationScrollFinished(Animation& source)
+{
+ mRefreshEnabled = true;
+ DoRefresh(GetCurrentLayoutPosition(0), true);
+}
+
void ItemView::OnOvershootOnFinished(Animation& animation)
{
mAnimatingOvershootOn = false;
}
mScrollStartedSignalV2.Emit(GetCurrentScrollPosition());
+ mRefreshEnabled = true;
}
void ItemView::RemoveAnimation(Animation& animation)
}
mScrollStartedSignalV2.Emit(GetCurrentScrollPosition());
+ mRefreshEnabled = true;
+}
+
+void ItemView::SetOvershootEffectColor( const Vector4& color )
+{
+ mOvershootEffectColor = color;
+ if( mOvershootOverlay )
+ {
+ mOvershootOverlay.SetColor( color );
+ }
}
void ItemView::SetOvershootEnabled( bool enable )
Actor self = Self();
if( enable )
{
- mOvershootEffect = BouncingEffect::New(Scrollable::DEFAULT_OVERSHOOT_COLOUR);
- mOvershootOverlay = CreateSolidColorActor(Vector4::ONE);
+ Property::Index effectOvershootPropertyIndex = Property::INVALID_INDEX;
+ mOvershootOverlay = CreateBouncingEffectActor( effectOvershootPropertyIndex );
+ mOvershootOverlay.SetColor(mOvershootEffectColor);
mOvershootOverlay.SetParentOrigin(ParentOrigin::TOP_LEFT);
mOvershootOverlay.SetAnchorPoint(AnchorPoint::TOP_LEFT);
mOvershootOverlay.SetDrawMode(DrawMode::OVERLAY);
- mOvershootOverlay.SetShaderEffect(mOvershootEffect);
self.Add(mOvershootOverlay);
- Constraint constraint = Constraint::New<float>( Actor::SIZE_WIDTH,
+
+ Constraint constraint = Constraint::New<Vector3>( Actor::SIZE,
ParentSource( mPropertyScrollDirection ),
Source( mScrollPositionObject, ScrollConnector::OVERSHOOT ),
ParentSource( Actor::SIZE ),
OvershootOverlaySizeConstraint() );
mOvershootOverlay.ApplyConstraint(constraint);
- mOvershootOverlay.SetSize(OVERSHOOT_BOUNCE_IMAGE_1_PIXEL_AREA.width, OVERSHOOT_BOUNCE_IMAGE_1_PIXEL_AREA.height);
+ mOvershootOverlay.SetSize(OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.width, OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE.height);
constraint = Constraint::New<Quaternion>( Actor::ROTATION,
ParentSource( mPropertyScrollDirection ),
OvershootOverlayVisibilityConstraint() );
mOvershootOverlay.ApplyConstraint(constraint);
- int effectOvershootPropertyIndex = mOvershootEffect.GetPropertyIndex(mOvershootEffect.GetProgressRatePropertyName());
Actor self = Self();
constraint = Constraint::New<float>( effectOvershootPropertyIndex,
Source( mScrollPositionObject, ScrollConnector::OVERSHOOT ),
EqualToConstraint() );
- mOvershootEffect.ApplyConstraint(constraint);
+ mOvershootOverlay.ApplyConstraint(constraint);
}
else
{
self.Remove(mOvershootOverlay);
mOvershootOverlay.Reset();
}
- mOvershootEffect.Reset();
}
}
range.end = mItemPool.rbegin()->first + 1;
}
+void ItemView::OnScrollPositionChanged( float position )
+{
+ // Cancel scroll animation to prevent any fighting of setting the scroll position property.
+ RemoveAnimation(mScrollAnimation);
+
+ // Refresh the cache immediately when the scroll position is changed.
+ DoRefresh(position, false); // No need to cache extra items.
+}
+
} // namespace Internal
} // namespace Toolkit