/* * Copyright (c) 2018 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include "shared/view.h" #include using namespace Dali; using namespace Dali::Toolkit; namespace { const Vector4 GRID_BACKGROUND_COLOR(0.85f, 0.85f, 0.85f, 1.0f); const Vector4 CONTROL_POINT1_COLOR(Color::MAGENTA); const Vector4 CONTROL_POINT2_COLOR(0.0, 0.9, 0.9, 1.0); const Vector3 CONTROL_POINT1_ORIGIN(-100, 200, 0); const Vector3 CONTROL_POINT2_ORIGIN( 100, -200, 0); const char* const CIRCLE1_IMAGE( DEMO_IMAGE_DIR "circle1.png" ); const char* const CIRCLE2_IMAGE( DEMO_IMAGE_DIR "circle2.png" ); const char* const ANIMATION_BACKGROUND( DEMO_IMAGE_DIR "slider-skin.9.png" ); const char* APPLICATION_TITLE("Bezier curve animation"); const float ANIM_LEFT_FACTOR(0.2f); const float ANIM_RIGHT_FACTOR(0.8f); const int AXIS_LABEL_POINT_SIZE(7); const float AXIS_LINE_SIZE(1.0f); const char* CURVE_VERTEX_SHADER = DALI_COMPOSE_SHADER ( attribute mediump vec2 aPosition; uniform mediump mat4 uMvpMatrix; uniform vec3 uSize; void main() { gl_Position = uMvpMatrix * vec4(aPosition*uSize.xy, 0.0, 1.0); } ); const char* CURVE_FRAGMENT_SHADER = DALI_COMPOSE_SHADER ( uniform lowp vec4 uColor; void main() { gl_FragColor = vec4(0.0,0.0,0.0,1.0); } ); inline float Clamp(float v, float min, float max) { if(vmax) return max; return v; } struct HandlePositionConstraint { HandlePositionConstraint( float minRelX, float maxRelX, float minRelY, float maxRelY ) : minRelX(minRelX), maxRelX(maxRelX), minRelY(minRelY), maxRelY(maxRelY) { } void operator()( Vector3& current, const PropertyInputContainer& inputs ) { Vector3 size( inputs[0]->GetVector3() ); current.x = Clamp(current.x, minRelX*size.x, maxRelX*size.x ); current.y = Clamp(current.y, minRelY*size.y, maxRelY*size.y ); } float minRelX; float maxRelX; float minRelY; float maxRelY; }; void AnimatingPositionConstraint( Vector3& current, const PropertyInputContainer& inputs ) { float positionFactor( inputs[0]->GetFloat() ); // -1 - 2 Vector3 size( inputs[1]->GetVector3() ); current.x = size.x * (positionFactor-0.5f); // size * (-1.5 - 1.5) } } //unnamed namespace class BezierCurveExample : public ConnectionTracker { public: BezierCurveExample( Application& application ) : mApplication( application ), mControlPoint1(), mControlPoint2(), mControlLine1(), mControlLine2(), mAnimIcon1(), mAnimIcon2(), mDragActor(), mCurve(), mCoefficientLabel(), mContentLayer(), mGrid(), mTimer(), mDragAnimation(), mBezierAnimation(), mCurveVertices(), mLine1Vertices(), mLine2Vertices(), mRelativeDragPoint(), mLastControlPointPosition1(), mLastControlPointPosition2(), mPositionFactorIndex(), mDuration( 2.0f ), mControlPoint1Id( 0.0f ), mControlPoint2Id( 0.0f ), mControlPointScale( 0.5f ), mControlPointZoomScale( mControlPointScale * 2.0 ), mGoingRight( true ) { // Connect to the Application's Init signal mApplication.InitSignal().Connect( this, &BezierCurveExample::Create ); } ~BezierCurveExample() { // Nothing to do here; } // The Init signal is received once (only) during the Application lifetime void Create( Application& application ) { // Hide the indicator bar application.GetWindow().ShowIndicator( Dali::Window::INVISIBLE ); Stage stage = Stage::GetCurrent(); stage.KeyEventSignal().Connect( this, &BezierCurveExample::OnKeyEvent ); CreateBackground(stage); mControlPointScale = 0.5f; mControlPointZoomScale = mControlPointScale * 2.0f; mContentLayer = Layer::New(); mContentLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); mContentLayer.TouchSignal().Connect(this, &BezierCurveExample::OnTouchLayer); mContentLayer.SetParentOrigin( ParentOrigin::CENTER ); stage.Add( mContentLayer ); // 6 rows: title, grid, coords, play, anim1, anim2 TableView contentLayout = TableView::New(5, 1); contentLayout.SetName("contentLayout"); contentLayout.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); contentLayout.SetCellPadding( Size( 30, 30 ) ); contentLayout.SetParentOrigin(ParentOrigin::TOP_CENTER); contentLayout.SetAnchorPoint(AnchorPoint::TOP_CENTER); mContentLayer.Add( contentLayout ); // Create a TextLabel for the application title. Toolkit::TextLabel label = Toolkit::TextLabel::New( APPLICATION_TITLE ); label.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" ); label.SetProperty( Toolkit::TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" ); label.SetProperty( Toolkit::TextLabel::Property::TEXT_COLOR, Color::BLACK ); contentLayout.Add( label ); contentLayout.SetFitHeight(0); mGrid = Control::New(); mGrid.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH ); mGrid.SetResizePolicy( ResizePolicy::DIMENSION_DEPENDENCY, Dimension::HEIGHT ); mGrid.SetParentOrigin(ParentOrigin::CENTER); mGrid.SetAnchorPoint(AnchorPoint::CENTER); mGrid.SetBackgroundColor(GRID_BACKGROUND_COLOR); contentLayout.Add( mGrid ); contentLayout.SetCellAlignment(1, HorizontalAlignment::CENTER, VerticalAlignment::CENTER ); CreateCubic(mGrid); CreateControlPoints( mGrid ); // Control points constrained to double height of grid CreateAxisLabels( mGrid ); mCoefficientLabel = TextLabel::New(); mCoefficientLabel.SetProperty( TextLabel::Property::ENABLE_MARKUP, true ); mCoefficientLabel.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" ); mCoefficientLabel.SetProperty( Toolkit::TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" ); mCoefficientLabel.SetParentOrigin(ParentOrigin::CENTER); contentLayout.Add( mCoefficientLabel ); SetLabel( Vector2(0,0), Vector2(1,1)); contentLayout.SetCellAlignment(2, HorizontalAlignment::CENTER, VerticalAlignment::CENTER ); contentLayout.SetFitHeight(2); // Setup Play button and 2 icons to show off current anim and linear anim PushButton play = PushButton::New(); play.SetName("Play"); play.SetParentOrigin(ParentOrigin::CENTER); play.SetLabelText("Play"); play.ClickedSignal().Connect( this, &BezierCurveExample::OnPlayClicked ); contentLayout.Add( play ); contentLayout.SetCellAlignment(3, HorizontalAlignment::CENTER, VerticalAlignment::CENTER ); contentLayout.SetFitHeight(3); auto animContainer = Control::New(); animContainer.SetName("AnimationContainer"); animContainer.SetParentOrigin( ParentOrigin::CENTER ); animContainer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); auto animRail = Control::New(); animRail.SetProperty( Control::Property::BACKGROUND, Property::Map() .Add( Visual::Property::TYPE, Visual::IMAGE ) .Add( ImageVisual::Property::URL, ANIMATION_BACKGROUND ) ); animRail.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS ); animRail.SetSizeModeFactor( Vector3( 0.666f, 0.2f, 1.0f ) ); animRail.SetParentOrigin( ParentOrigin::CENTER ); animContainer.Add( animRail ); contentLayout.Add( animContainer ); contentLayout.SetFixedHeight(4, 150 ); mAnimIcon1 = ImageView::New( CIRCLE1_IMAGE ); mAnimIcon1.SetParentOrigin( ParentOrigin::CENTER ); mAnimIcon1.SetAnchorPoint( AnchorPoint::CENTER ); // Would like some means of setting and animating position as a percentage of // parent size without using constraints, but this will have to suffice for the moment. mPositionFactorIndex = mAnimIcon1.RegisterProperty( "positionFactor", ANIM_LEFT_FACTOR); // range: 0-1 (+/- 1) Constraint constraint = Constraint::New( mAnimIcon1, Actor::Property::POSITION, AnimatingPositionConstraint ); constraint.AddSource( Source( mAnimIcon1, mPositionFactorIndex ) ); constraint.AddSource( Source( animContainer, Actor::Property::SIZE ) ); constraint.Apply(); animContainer.Add( mAnimIcon1 ); // First UpdateCurve needs to run after size negotiation and after images have loaded mGrid.OnRelayoutSignal().Connect( this, &BezierCurveExample::InitialUpdateCurve ); auto controlPoint1 = Control::DownCast( mControlPoint1 ); if( controlPoint1 ) { controlPoint1.ResourceReadySignal().Connect( this, &BezierCurveExample::ControlPointReady ); } auto controlPoint2 = Control::DownCast( mControlPoint2 ); if( controlPoint2 ) { controlPoint2.ResourceReadySignal().Connect( this, &BezierCurveExample::ControlPointReady ); } } void ControlPointReady( Control control ) { UpdateCurve(); } void InitialUpdateCurve(Actor actor) { UpdateCurve(); } void CreateBackground( Stage stage ) { Toolkit::Control background = Dali::Toolkit::Control::New(); background.SetAnchorPoint( Dali::AnchorPoint::CENTER ); background.SetParentOrigin( Dali::ParentOrigin::CENTER ); background.SetResizePolicy( Dali::ResizePolicy::FILL_TO_PARENT, Dali::Dimension::ALL_DIMENSIONS ); Property::Map map; map.Insert( Visual::Property::TYPE, Visual::COLOR ); map.Insert( ColorVisual::Property::MIX_COLOR, Vector4( 253/255.0f, 245/255.0f, 230/255.0f, 1.0f ) ); background.SetProperty( Dali::Toolkit::Control::Property::BACKGROUND, map ); stage.Add( background ); } void CreateCubic(Actor parent) { // Create a mesh to draw the cubic as a single line mCurve = Actor::New(); mCurve.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); mCurve.SetParentOrigin( ParentOrigin::CENTER ); Shader shader = Shader::New( CURVE_VERTEX_SHADER, CURVE_FRAGMENT_SHADER ); Property::Map curveVertexFormat; curveVertexFormat["aPosition"] = Property::VECTOR2; mCurveVertices = PropertyBuffer::New( curveVertexFormat ); Vector2 vertexData[2] = { Vector2(-0.5f, 0.5f), Vector2( 0.5f, -0.5f ) }; mCurveVertices.SetData( vertexData, 2 ); Geometry geometry = Geometry::New(); geometry.AddVertexBuffer( mCurveVertices ); geometry.SetType( Geometry::LINE_STRIP ); Renderer renderer = Renderer::New( geometry, shader ); mCurve.AddRenderer( renderer ); parent.Add(mCurve); } Actor CreateControlPoint( Actor parent, const char* url, Vector3 position) { Actor actor = ImageView::New( url ); actor.SetScale( mControlPointScale); actor.SetParentOrigin( ParentOrigin::CENTER ); // Curve and line drawing works off current value (i.e. last update frame's value). Need to animate to ensure // initial position is baked to both frames before initially drawing the curve. auto positionAnimation = Animation::New( 0.01f ); positionAnimation.AnimateTo( Property( actor, Actor::Property::POSITION ), position, AlphaFunction::EASE_IN_OUT ); positionAnimation.Play(); positionAnimation.FinishedSignal().Connect( this, &BezierCurveExample::OnAnimationFinished ); // Set up constraints for drag/drop Constraint constraint = Constraint::New( actor, Actor::Property::POSITION, HandlePositionConstraint( -0.5, 0.5, -1, 1)); constraint.AddSource( Source( parent, Actor::Property::SIZE ) ); constraint.Apply(); actor.TouchSignal().Connect(this, &BezierCurveExample::OnTouchControlPoint); return actor; } Actor CreateControlLine( PropertyBuffer vertexBuffer ) { Actor line = Actor::New(); line.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); line.SetParentOrigin( ParentOrigin::CENTER ); Shader shader = Shader::New( CURVE_VERTEX_SHADER, CURVE_FRAGMENT_SHADER ); Geometry geometry = Geometry::New(); geometry.AddVertexBuffer( vertexBuffer ); geometry.SetType( Geometry::LINE_STRIP ); Renderer renderer = Renderer::New( geometry, shader ); line.AddRenderer( renderer ); return line; } void CreateControlPoints( Actor parent ) { mControlPoint1 = CreateControlPoint( parent, CIRCLE1_IMAGE, CONTROL_POINT1_ORIGIN ); mControlPoint1Id = mControlPoint1.GetId(); mControlPoint2 = CreateControlPoint( parent, CIRCLE2_IMAGE, CONTROL_POINT2_ORIGIN ); mControlPoint2Id = mControlPoint2.GetId(); Property::Map lineVertexFormat; lineVertexFormat["aPosition"] = Property::VECTOR2; mLine1Vertices = PropertyBuffer::New( lineVertexFormat ); mLine2Vertices = PropertyBuffer::New( lineVertexFormat ); mControlLine1 = CreateControlLine( mLine1Vertices ); mControlLine2 = CreateControlLine( mLine2Vertices ); parent.Add( mControlLine1 ); parent.Add( mControlLine2 ); parent.Add( mControlPoint1 ); parent.Add( mControlPoint2 ); } void CreateAxisLabels( Actor parent ) { TextLabel progressionLabel = TextLabel::New( "Progression" ); progressionLabel.SetProperty( TextLabel::Property::POINT_SIZE, AXIS_LABEL_POINT_SIZE ); progressionLabel.SetOrientation( Degree(-90.0f), Vector3::ZAXIS ); progressionLabel.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT ); progressionLabel.SetParentOrigin( ParentOrigin::BOTTOM_LEFT ); CreateLine( progressionLabel, ParentOrigin::BOTTOM_LEFT ); TextLabel timeLabel = TextLabel::New( "Time" ); timeLabel.SetProperty( TextLabel::Property::POINT_SIZE, AXIS_LABEL_POINT_SIZE ); timeLabel.SetAnchorPoint( AnchorPoint::TOP_LEFT ); timeLabel.SetParentOrigin( ParentOrigin::BOTTOM_LEFT ); CreateLine( timeLabel, ParentOrigin::TOP_LEFT ); parent.Add( progressionLabel ); parent.Add( timeLabel ); } void CreateLine( Actor parent, const Vector3& parentOrigin ) { Control control = Control::New(); control.SetAnchorPoint( AnchorPoint::TOP_LEFT ); control.SetParentOrigin( parentOrigin ); control.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH ); control.SetProperty( Actor::Property::SIZE_HEIGHT, AXIS_LINE_SIZE ); control.SetBackgroundColor( Color::BLACK ); parent.Add( control ); } void SetLabel( Vector2 pos1, Vector2 pos2 ) { std::ostringstream oss; oss.setf(std::ios::fixed, std::ios::floatfield); oss.precision(2); oss << "( " << pos1.x << ", " << pos1.y << ", "; oss << "" << pos2.x << ", " << pos2.y << ""; oss << " )"; mCoefficientLabel.SetProperty( TextLabel::Property::TEXT, oss.str() ); } Vector2 AlignToGrid( Vector3 actorPos, Vector3 gridSize ) { actorPos /= gridSize; // => -0.5 - 0.5 actorPos.x = Clamp( actorPos.x, -0.5f, 0.5f ); return Vector2( actorPos.x + 0.5f, 0.5f - actorPos.y ); } void GetControlPoints(Vector2& pt1, Vector2& pt2) { Vector3 gridSize = mGrid.GetProperty( Actor::Property::SIZE ); // Get target value pt1 = AlignToGrid( mControlPoint1.GetCurrentPosition(), gridSize ); pt2 = AlignToGrid( mControlPoint2.GetCurrentPosition(), gridSize ); } /** * @param[in] actor The actor to get the position from * @param[out] point The point in the grid in the range -0.5 -> 0.5 in x and y, with y up. * @param[out] position The actor position, floored to the nearest pixel */ void GetPoint( Actor actor, Vector2& point, Vector2& position) { auto gridSize = mGrid.GetProperty( Actor::Property::SIZE ); // Get target value auto currentPosition = actor.GetCurrentPosition(); // Get constrained current value position = Vector2( std::floor( currentPosition.x ), std::floor( currentPosition.y ) ); point.x = Clamp( position.x / gridSize.x, -0.5f, 0.5f ) + 0.5f; point.y = 0.5f - position.y / gridSize.y; } void UpdateCurve() { Vector2 point1, point2; Vector2 position1, position2; const int NUMBER_OF_SEGMENTS(40); GetPoint( mControlPoint1, point1, position1 ); GetPoint( mControlPoint2, point2, position2 ); if( position1 != mLastControlPointPosition1 || position2 != mLastControlPointPosition2 ) { mLastControlPointPosition1 = position1; mLastControlPointPosition2 = position2; SetLabel( point1, point2 ); Path path = Path::New(); path.AddPoint(Vector3::ZERO); path.AddPoint(Vector3(1.0f, 1.0f, 1.0f)); path.AddControlPoint( Vector3( point1.x, point1.y, 0 ) ); path.AddControlPoint( Vector3( point2.x, point2.y, 0 ) ); Dali::Vector verts; verts.Resize(2*(NUMBER_OF_SEGMENTS+1)); // 1 more point than segment for( int i=0; i<=NUMBER_OF_SEGMENTS; ++i) { Vector3 position, tangent; path.Sample( i/float(NUMBER_OF_SEGMENTS), position, tangent ); verts[i*2] = position.x-0.5; verts[i*2+1] = 0.5-position.y; } mCurveVertices.SetData(&verts[0], NUMBER_OF_SEGMENTS+1); Vector4 line1( -0.5f, 0.5f, point1.x-0.5f, 0.5f-point1.y ); mLine1Vertices.SetData( line1.AsFloat(), 2 ); Vector4 line2( 0.5f, -0.5f, point2.x-0.5f, 0.5f-point2.y ); mLine2Vertices.SetData( line2.AsFloat(), 2 ); } } bool OnTouchControlPoint( Actor controlPoint, const TouchData& event ) { if( event.GetPointCount() > 0 ) { if( event.GetState( 0 ) == PointState::DOWN ) { Vector2 screenPoint = event.GetScreenPosition( 0 ); mRelativeDragPoint = screenPoint; mRelativeDragPoint -= Vector2(controlPoint.GetCurrentPosition()); mDragActor = controlPoint; mDragAnimation = Animation::New(0.25f); mDragAnimation.AnimateTo( Property(mDragActor, Actor::Property::SCALE), Vector3( mControlPointZoomScale, mControlPointZoomScale, 1.0f), AlphaFunction::EASE_OUT); mDragAnimation.Play(); } } return false; // Don't mark this as consumed - let the layer get the touch } bool OnTouchLayer( Actor actor, const TouchData& event ) { if( event.GetPointCount() > 0 ) { if( mDragActor ) { Vector3 position( event.GetScreenPosition( 0 ) ); mDragActor.SetPosition( position - Vector3( mRelativeDragPoint ) ); if( event.GetState( 0 ) == PointState::UP ) // Stop dragging { mDragAnimation = Animation::New(0.25f); mDragAnimation.AnimateTo( Property( mDragActor, Actor::Property::SCALE ), Vector3( mControlPointScale, mControlPointScale, 1.0f), AlphaFunction::EASE_IN); mDragAnimation.FinishedSignal().Connect( this, &BezierCurveExample::OnAnimationFinished ); mDragAnimation.Play(); mDragActor.Reset(); } } UpdateCurve(); } return false; } void OnAnimationFinished( Animation& animation ) { UpdateCurve(); } bool OnPlayClicked( Button button ) { if( ! mBezierAnimation ) { mBezierAnimation = Animation::New( mDuration ); } mBezierAnimation.Stop(); mBezierAnimation.Clear(); float positionFactor = ANIM_LEFT_FACTOR; if( mGoingRight ) { positionFactor = ANIM_RIGHT_FACTOR; mGoingRight = false; } else { mGoingRight = true; } Vector2 pt1, pt2; GetControlPoints(pt1, pt2); mBezierAnimation.AnimateTo( Property(mAnimIcon1, mPositionFactorIndex), positionFactor, AlphaFunction( pt1, pt2 ) ); mBezierAnimation.Play(); return true; } /** * Main key event handler */ void OnKeyEvent(const KeyEvent& event) { if( event.state == KeyEvent::Down && (IsKey( event, DALI_KEY_ESCAPE) || IsKey( event, DALI_KEY_BACK )) ) { mApplication.Quit(); } } private: Application& mApplication; Actor mControlPoint1; Actor mControlPoint2; Actor mControlLine1; Actor mControlLine2; ImageView mAnimIcon1; ImageView mAnimIcon2; Actor mDragActor; Actor mCurve; TextLabel mCoefficientLabel; Layer mContentLayer; Control mGrid; Timer mTimer; Animation mDragAnimation; Animation mBezierAnimation; PropertyBuffer mCurveVertices; PropertyBuffer mLine1Vertices; PropertyBuffer mLine2Vertices; Vector2 mRelativeDragPoint; Vector2 mLastControlPointPosition1; Vector2 mLastControlPointPosition2; Property::Index mPositionFactorIndex; float mDuration; unsigned int mControlPoint1Id; unsigned int mControlPoint2Id; float mControlPointScale; float mControlPointZoomScale; bool mGoingRight; }; int main( int argc, char **argv ) { Application application = Application::New( &argc, &argv ); BezierCurveExample test( application ); application.MainLoop(); return 0; }