From 34c45dcde92eaac27f376a0eea27c6ae1bbeb15a Mon Sep 17 00:00:00 2001 From: David Steele Date: Tue, 25 Oct 2016 16:11:27 +0100 Subject: [PATCH] Bezier curve animation demo Added a demo to illustrate the control points needed for a bezier curve based alpha-function. Change-Id: I9ca14352e297b4725287387b92bd218323af7c3a --- com.samsung.dali-demo.xml | 3 + demo/dali-demo.cpp | 1 + examples/bezier-curve/bezier-curve-example.cpp | 565 +++++++++++++++++++++++++ resources/images/circle1.png | Bin 0 -> 1386 bytes resources/images/circle2.png | Bin 0 -> 469 bytes resources/images/circle_point.png | Bin 0 -> 1485 bytes resources/images/circle_stroke_point.png | Bin 0 -> 1852 bytes resources/images/slider-skin.9.png | Bin 0 -> 656 bytes resources/po/en_GB.po | 3 + resources/po/en_US.po | 3 + shared/dali-demo-strings.h | 2 + 11 files changed, 577 insertions(+) create mode 100644 examples/bezier-curve/bezier-curve-example.cpp create mode 100644 resources/images/circle1.png create mode 100644 resources/images/circle2.png create mode 100644 resources/images/circle_point.png create mode 100644 resources/images/circle_stroke_point.png create mode 100644 resources/images/slider-skin.9.png diff --git a/com.samsung.dali-demo.xml b/com.samsung.dali-demo.xml index f0155e0..c438eb6 100644 --- a/com.samsung.dali-demo.xml +++ b/com.samsung.dali-demo.xml @@ -22,6 +22,9 @@ + + + diff --git a/demo/dali-demo.cpp b/demo/dali-demo.cpp index b1b45cd..379758f 100644 --- a/demo/dali-demo.cpp +++ b/demo/dali-demo.cpp @@ -37,6 +37,7 @@ int DALI_EXPORT_API main(int argc, char **argv) DaliTableView demo(app); demo.AddExample(Example("blocks.example", DALI_DEMO_STR_TITLE_BLOCKS)); + demo.AddExample(Example("bezier-curve.example", DALI_DEMO_STR_TITLE_BEZIER_CURVE)); demo.AddExample(Example("bubble-effect.example", DALI_DEMO_STR_TITLE_BUBBLES)); demo.AddExample(Example("contact-cards.example", DALI_DEMO_STR_TITLE_CONTACT_CARDS)); demo.AddExample(Example("cube-transition-effect.example", DALI_DEMO_STR_TITLE_CUBE_TRANSITION)); diff --git a/examples/bezier-curve/bezier-curve-example.cpp b/examples/bezier-curve/bezier-curve-example.cpp new file mode 100644 index 0000000..b83021d --- /dev/null +++ b/examples/bezier-curve/bezier-curve-example.cpp @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2016 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 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 ), + mDuration(2.0f), + 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 + + 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 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; +} diff --git a/resources/images/circle1.png b/resources/images/circle1.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0a69079830266c3fbf482e2b0930bc035bcd86 GIT binary patch literal 1386 zcmV-w1(o`VP)K`e4J5jk!GIbqY0SdRQx&p&32v%v$HN_y2>#Je|GI z`F*o4d!4=i3j{$B1VIo4K@fzh(p0Y>EKAZRU>?xv{>}sD09OH*1D66VzzA>(C;@*0 z``l|UutypFn+PaB^s9j`UDwgTS{yAFx9iT@nEkLvI0A0&9RffaZv&`v=$w zYz2Ch(Ps)PAWPC&z_Y*xU`7o-!Vs_#cwZTPvM>U&B%J|h;7Qkq*P0Tr33y8x{a0=U zxIy(1;B_~U)*FMsGs@_m+zH5%bT05Au($zchP}WBW%Pku2*{H3LEr!SeT;~ zgYSSYW%QA{7LXvXGq6k zroJs;rG!5vR{#&h?t<1x&gW%qv@XakH4hT^zMV#A6u4FyJrtt_bV>MS<=GXh3%X14 z4WCn4U65P%Daim_7Q;ZIj2;cy0v1U4Wzzx758VYVl3cyGHMD?E$<&L^&;sU2re4er zE#P{|)QcNJ3rHkWFOtv#+9gvj+CvLylT5v63oW2YGWDV{JF+BkA$w7r z2it&GLLDq*&A4{#TKK{ohrbKByOJy3=mMOC1Y0%nrW}U<18`@^f_)*op#R1LgRKw! zea^z~2bPBpKezxVVHjABE#8_3J_GJiMxTgr@F)*nE2Bq%`>+dljmjwSD)4Z~K0oRh zFvYmA8oN^1NY5A?1Xe4f`(vLddg%dW^vBr8Tz5A}_&b0_)d;^TEnr+rXad#)DYmpr zy)lSgSld?3lh$evnHRBp$kapSts~e&9;CR@{BWrjRiUxEEXUWl5lxG31sI`hc%vY{HNm s0h0{cx!+4t5ClOG1VIo4K@f_?zcyJ>rh;*>wEzGB07*qoM6N<$f)){HYybcN literal 0 HcmV?d00001 diff --git a/resources/images/circle2.png b/resources/images/circle2.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc73a670dd68919d8c20d77ee9a8a89035ac544 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPd6FVOVx9Tm{Dh39|Bu^K|kcwMx?>;O% zLFz5{BHiXn)Tj%V7JZO)net$>kk^k$LDfHNT7+uCTsZBu7q-lw8gS^mm&U_%)hQn~ z3(1KunzGvdc9T$Dm=jR?pl!k<+XGkkuVa-mSj)KX-&WlV4W7IOU)Se1vb88CoZ>L5 z`*&vw0B58lna0#QE0HZT10zcV1Czr6n`!l$_iLZ`u{AWT z{E?ec@GrYTkfr_tN1r_Jj2jHP5x*lnnbpi3xUbo2UT8@8Q7`o1XFvsuq$}^XrE_!+ z9#T_&aYt;+ZOQUUE!(1n{U=E@8 zEbF-Oi)ZFJr9Laym6JObpBMB#Sup9|S)-bKaA-NG*D)^QTrIYDN4FIy^gLbtT-G@y GGywpzJ+vYK literal 0 HcmV?d00001 diff --git a/resources/images/circle_point.png b/resources/images/circle_point.png new file mode 100644 index 0000000000000000000000000000000000000000..1fa2151c9111e10ac76950bf1bc80ee720cce81d GIT binary patch literal 1485 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`XlmwcV(#W*?qq6dXy|I>WMt&%>g;T6v4~Pj*wm=R%;iu*SQ+p9GS!bR&-o?PcY<=ZFL-s!R~b6w)jW2#RuBRFuxCD6pTEk(JezYx^{5@oAIlA1E2w zF6y`xVRY@Xwlmwwt6+OfZw`{k1bDZ+3c;kFdg1iwSQ^tnR0{WN(d{-cftFTB(VJ zXWp|_k1h5u=DM-%FZV|6Gl@U;WX-(vOP49%@ad{ViB65LeX6BCm+u_fz<*;)QQfs) zZ0DWNbleFzZ&eDP+P+T@$AZ`j@{7P5v;Jej(Utww9ASGuBfZj6qYzeZbf=ixb#%NR`0 z@-gX8m}{(DygEQ*&f)LI$Je|{mAa8J=cN0CY^%9;2O7Wcz5Vvu#vJD<8*ab;`s&9w z<*8n>2cC0(XiT0|wRo?^x=OLackL?gIoH0NRCH}ZV6XLo_;tUlwNzHsA6U9=#)m8> zC&9>sN2zsc7S=}m5=>&<5jsCy&Xv0?IMBVjVOC$~bIH0$;gihCC;ZQ=hwIN#VBGrB z=h&RGd){$s{qvQdwsp4(UT95=o2j)??}NqxhK(s3Vm2fzCxHrBPgg&ebxsLQ0CwXq AcK`qY literal 0 HcmV?d00001 diff --git a/resources/images/circle_stroke_point.png b/resources/images/circle_stroke_point.png new file mode 100644 index 0000000000000000000000000000000000000000..c81d7a0d3234eb1198572dee57c0b734e64017e7 GIT binary patch literal 1852 zcmaJ?c~}x@940I6$+fJ^qfMQ#P&0)AK_N*IY}Eu6DJe}w7?l);U{p|R)y$*PF0*61 zST(I&SF7!K?4SdCH0eGzSuOL3%1XC#SF7C}Ol|*Icb=K~zVG+E@B2IFnW8YxcNSCU zOvPX@7D0h5F1q53&lFSiT}^*ci!OFZ03V5f6OeR)9KtX}a6AMANd$=y7ZQk6Ynvb+ z3}zx$%;O_`b_h)fOFRTdjE7PpL)jRNkFQcD5GF$i5Dz7arF87uhD%sLETUs0$!r2! z#)Oi@ff;frB7?&dW+V%#BCPKcz(+|#1tbt60F;sxse-1YV~2HV=-z0?V}W4^l1#^b z35w4S1DLQJ0>~aDcOd};0Sd(f^dwU#;39xX0Eu`4dQ;p%Pa2s*BYFWN7Z&v<7bVcR ztfeEq&=Vb-gdj2+9-p3`?vYONfaQsJkV>T*HHbua6ydH=NfCk4U8-;zRbW91pEy=tw6ZY*LeR^ zSiw`tAUqdRz^QT}+PDNKqbnJWDTf3IEa$;+%4ikCl3)Z@B*8L(89@PDBE?b>oUU*k z#wEQE3OM!2H!V*$tI;?ZF6 z#>W1u(W54GdW_q#u|*GKgAYm3X_up8ZKd@uM8B50AQpqCZ0+Ttq=Ae zYO5tdW#=ES?=4GnGkemd#+x5d4eV7VCYJ4n_AcJM?CDLzP-smX^yX=URO8K<#5ZI( zY%}RKS#Oed>YIL_*pH%j2Vm0RohuS-=iNd3`L>g5%3-HM9j0eO+R$f=C9bMEtfV$7 z@bZmparTB7*oL5%>1R2a-EYciaMJ#8yGJ&f%_T+4k3T5L1)?`d?D|am&aDje zx$$(&qy6Srbbi3?ZOp4-xh?5Mwzxlc zMs$@P+@HC{*(Pfpac{_5PiB)NzI)P7-}-HG`r%dAtpHY30rAkShK@tmt2qJGPHXG- zCkFGZ7;#WY<%+XrTdxrY?IVw`sItAL*IIGQBYE8?T|u8>?CVMg%bf;f)4okPHNm^L zy`E5C)10W#{v|MfxC*RxQ?*VT$Znw?i=C{KhcDc9_tfl6zm|O8=I#>}RVCUzX77D=w(P?B1&nrcXKZn{%iW3)Q-!`1(BqA3rb%G-x!yTj zgInF0_Uq#SF6Ts(HojgLN8qoi;IH;=bo}F7;Mvd}6=qIOP7eFB52mU&r*f0&Qwma| zthBrN>OgSi$>(O$z?Qr&jb_J#H5Yuy>tpnHg^zP;so=Q%+= zzotdgz_y6xlKWx~%d^FVO*V>i(YU$O3bK|wvEF{nbz^^WW>&;@(&D)#ImN}rswr11 zJ32bT>2iAq2M0gHfQ_|v?zUbP_eI|Qqh>L<8r(*pr>V8I_1gXWrL(Tc*Cr(y&Oc6Z zIQ%@O!(cE>&-*)a{>;Zw(b2!Dqq$R-yWHt6iJd{{vdT4K^5Xt#1gQ~Fp-bjU_Axtf z_0Q_kp{0``Y`8j9efb_^UgP?QGhViS1ZQ7f$gG|LmQW>Ku5ey&nj75S=(m4fB$|KqAY1>>(R>0DO(;DoA#9$ zOJFz*h*L8QPLx+S`V+LQt!lzZI3%CSbLwy0)b`B0Bjm zH7o+*f|9BOSS>0ltvgOFOF%d)r{o=0i!yUcUQ*K{V3PlX)uOn>%wN>B2!yd(1WeNZ z=weZNRuO$Hs;zJPNli;q)AO-g)YCupDK#yss%gfa7-r91ye=U*hiVp0oHXMxR*P1w zUhmdFaXM8pY*A^=|3yodPseHzu*Bt9wQBv&lJYu=ECGee!bK~etzW;{3}>ObYSlW+ z6|2^N%FZh#H(7!h(tMVflJkGTqUC=#ZP__( z%a$#I1S`I6+qSW9-MS-c&%Ps{Cr_RIKQ1u~e}xyB^uNBbR<;XHy{BW!HUEJS_Sa{0000