Dali 3D User Interface Engine
animation-impl.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 // CLASS HEADER
21 
22 // EXTERNAL INCLUDES
23 
24 // INTERNAL INCLUDES
41 
45 
46 namespace Dali
47 {
48 
49 namespace Internal
50 {
51 
52 static bool SHOW_VALUE = true;
53 static bool HIDE_VALUE = false;
54 
55 namespace
56 {
57 
58 // Signals
59 
60 const char* const SIGNAL_FINISHED = "finished";
61 
62 // Actions
63 
64 const char* const ACTION_PLAY = "play";
65 const char* const ACTION_STOP = "stop";
66 const char* const ACTION_PAUSE = "pause";
67 
69 {
70  return Dali::Animation::New(0.f);
71 }
72 
74 
76 
79 TypeAction action3( mType, ACTION_PAUSE, &Animation::DoAction );
80 
85 
86 } // anon namespace
87 
88 
89 AnimationPtr Animation::New(float durationSeconds)
90 {
91  Stage* stage = Stage::GetCurrent();
92 
93  if( stage )
94  {
95  AnimationPlaylist& playlist = stage->GetAnimationPlaylist();
96 
97  if( durationSeconds < 0.0f )
98  {
99  DALI_LOG_WARNING("duration should be greater than 0.0f.\n");
100  durationSeconds = 0.0f;
101  }
102 
103  AnimationPtr animation = new Animation( *stage, playlist, durationSeconds, DEFAULT_END_ACTION, DEFAULT_DISCONNECT_ACTION, DEFAULT_ALPHA_FUNCTION );
104 
105  // Second-phase construction
106  animation->Initialize();
107 
108  return animation;
109  }
110  else
111  {
112  return NULL;
113  }
114 }
115 
116 Animation::Animation( EventThreadServices& eventThreadServices, AnimationPlaylist& playlist, float durationSeconds, EndAction endAction, EndAction disconnectAction, AlphaFunction defaultAlpha )
117 : mEventThreadServices( eventThreadServices ),
118  mPlaylist( playlist ),
119  mAnimation( NULL ),
120  mNotificationCount( 0 ),
121  mFinishedCallback( NULL ),
122  mFinishedCallbackObject( NULL ),
123  mDurationSeconds( durationSeconds ),
124  mSpeedFactor(1.0f),
125  mLoopCount(1),
126  mCurrentLoop(0),
127  mPlayRange( Vector2(0.0f,1.0f)),
128  mEndAction( endAction ),
129  mDisconnectAction( disconnectAction ),
130  mDefaultAlpha( defaultAlpha ),
131  mState(Dali::Animation::STOPPED)
132 {
133 }
134 
136 {
137  // Connect to the animation playlist
138  mPlaylist.AnimationCreated( *this );
139 
141 
142  RegisterObject();
143 }
144 
146 {
147  // Guard to allow handle destruction after Core has been destroyed
148  if ( Stage::IsInstalled() )
149  {
150  // Disconnect from the animation playlist
151  mPlaylist.AnimationDestroyed( *this );
152 
154 
156  }
157 }
158 
160 {
161  DALI_ASSERT_DEBUG( mAnimation == NULL );
162 
163  // Create a new animation, temporarily owned
165 
166  // Keep a const pointer to the animation.
167  mAnimation = animation;
168 
169  // Transfer animation ownership to the update manager through a message
171 }
172 
174 {
175  if ( mAnimation != NULL )
176  {
177  // Remove animation using a message to the update manager
179  mAnimation = NULL;
180  }
181 }
182 
183 void Animation::SetDuration(float seconds)
184 {
185  if( seconds < 0.0f )
186  {
187  DALI_LOG_WARNING("duration should be greater than 0.0f.\n");
188  seconds = 0.0f;
189  }
190 
191  // Cache for public getters
192  mDurationSeconds = seconds;
193 
194  // mAnimation is being used in a separate thread; queue a message to set the value
196 }
197 
199 {
200  // This is not animatable; the cached value is up-to-date.
201  return mDurationSeconds;
202 }
203 
205 {
206  SetLoopCount( on ? 0 : 1 );
207 }
208 
209 void Animation::SetLoopCount(int count)
210 {
211  // Cache for public getters
212  mLoopCount = count;
213 
214  // mAnimation is being used in a separate thread; queue a message to set the value
216 }
217 
219 {
220  return mLoopCount;
221 }
222 
224 {
225  return mCurrentLoop;
226 }
227 
229 {
230  return mLoopCount != 1;
231 }
232 
234 {
235  // Cache for public getters
236  mEndAction = action;
237 
238  // mAnimation is being used in a separate thread; queue a message to set the value
240 }
241 
243 {
244  // This is not animatable; the cached value is up-to-date.
245  return mEndAction;
246 }
247 
249 {
250  // Cache for public getters
251  mDisconnectAction = action;
252 
253  // mAnimation is being used in a separate thread; queue a message to set the value
255 }
256 
258 {
259  // This is not animatable; the cached value is up-to-date.
260  return mDisconnectAction;
261 }
262 
264 {
265  // Update the current playlist
266  mPlaylist.OnPlay( *this );
267 
269 
270  // mAnimation is being used in a separate thread; queue a Play message
272 }
273 
274 void Animation::PlayFrom( float progress )
275 {
276  if( progress >= mPlayRange.x && progress <= mPlayRange.y )
277  {
278  // Update the current playlist
279  mPlaylist.OnPlay( *this );
280 
282 
283  // mAnimation is being used in a separate thread; queue a Play message
285  }
286 }
287 
289 {
291 
292  // mAnimation is being used in a separate thread; queue a Pause message
294 }
295 
297 {
298  return mState;
299 }
300 
302 {
304 
305  // mAnimation is being used in a separate thread; queue a Stop message
307 }
308 
310 {
312 
313  // Remove all the connectors
314  mConnectors.Clear();
315 
316  // Replace the old scene-object with a new one
319 
320  // Reset the notification count, since the new scene-object has never been played
321  mNotificationCount = 0;
322 
323  // Update the current playlist
324  mPlaylist.OnClear( *this );
325 }
326 
327 void Animation::AnimateBy(Property& target, Property::Value& relativeValue)
328 {
329  AnimateBy(target, relativeValue, mDefaultAlpha, TimePeriod(mDurationSeconds));
330 }
331 
332 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, AlphaFunction alpha)
333 {
334  AnimateBy(target, relativeValue, alpha, TimePeriod(mDurationSeconds));
335 }
336 
337 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, TimePeriod period)
338 {
339  AnimateBy(target, relativeValue, mDefaultAlpha, period);
340 }
341 
342 void Animation::AnimateBy(Property& target, Property::Value& relativeValue, AlphaFunction alpha, TimePeriod period)
343 {
344  Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
345 
346  ExtendDuration( period );
347 
348  switch ( relativeValue.GetType() )
349  {
350  case Property::BOOLEAN:
351  {
353  target.propertyIndex,
354  target.componentIndex,
355  new AnimateByBoolean(relativeValue.Get<bool>()),
356  alpha,
357  period ) );
358  break;
359  }
360 
361  case Property::INTEGER:
362  {
364  target.propertyIndex,
365  target.componentIndex,
366  new AnimateByInteger(relativeValue.Get<int>()),
367  alpha,
368  period ) );
369  break;
370  }
371 
372  case Property::FLOAT:
373  {
375  target.propertyIndex,
376  target.componentIndex,
377  new AnimateByFloat(relativeValue.Get<float>()),
378  alpha,
379  period ) );
380  break;
381  }
382 
383  case Property::VECTOR2:
384  {
386  target.propertyIndex,
387  target.componentIndex,
388  new AnimateByVector2(relativeValue.Get<Vector2>()),
389  alpha,
390  period ) );
391  break;
392  }
393 
394  case Property::VECTOR3:
395  {
397  target.propertyIndex,
398  target.componentIndex,
399  new AnimateByVector3(relativeValue.Get<Vector3>()),
400  alpha,
401  period ) );
402  break;
403  }
404 
405  case Property::VECTOR4:
406  {
408  target.propertyIndex,
409  target.componentIndex,
410  new AnimateByVector4(relativeValue.Get<Vector4>()),
411  alpha,
412  period ) );
413  break;
414  }
415 
416  case Property::ROTATION:
417  {
418  AngleAxis angleAxis = relativeValue.Get<AngleAxis>();
419 
421  target.propertyIndex,
422  target.componentIndex,
423  new RotateByAngleAxis(angleAxis.angle, angleAxis.axis),
424  alpha,
425  period ) );
426  break;
427  }
428 
429  default:
430  DALI_ASSERT_ALWAYS( false && "Property type enumeration out of bounds" ); // should never come here
431  break;
432  }
433 }
434 
435 void Animation::AnimateTo(Property& target, Property::Value& destinationValue)
436 {
437  AnimateTo(target, destinationValue, mDefaultAlpha, TimePeriod(mDurationSeconds));
438 }
439 
440 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, AlphaFunction alpha)
441 {
442  AnimateTo(target, destinationValue, alpha, TimePeriod(mDurationSeconds));
443 }
444 
445 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, TimePeriod period)
446 {
447  AnimateTo(target, destinationValue, mDefaultAlpha, period);
448 }
449 
450 void Animation::AnimateTo(Property& target, Property::Value& destinationValue, AlphaFunction alpha, TimePeriod period)
451 {
452  Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
453 
454  AnimateTo( object, target.propertyIndex, target.componentIndex, destinationValue, alpha, period );
455 }
456 
457 void Animation::AnimateTo(Object& targetObject, Property::Index targetPropertyIndex, int componentIndex, Property::Value& destinationValue, AlphaFunction alpha, TimePeriod period)
458 {
459  Property::Type type = targetObject.GetPropertyType(targetPropertyIndex);
460  if(componentIndex != Property::INVALID_COMPONENT_INDEX)
461  {
462  if( type == Property::VECTOR2
463  || type == Property::VECTOR3
464  || type == Property::VECTOR4 )
465  {
466  type = Property::FLOAT;
467  }
468  }
469  DALI_ASSERT_ALWAYS( type == destinationValue.GetType() && "DestinationValue does not match Target Property type" );
470 
471  ExtendDuration( period );
472 
473  switch (destinationValue.GetType())
474  {
475  case Property::BOOLEAN:
476  {
478  targetPropertyIndex,
479  componentIndex,
480  new AnimateToBoolean( destinationValue.Get<bool>() ),
481  alpha,
482  period ) );
483  break;
484  }
485 
486  case Property::INTEGER:
487  {
489  targetPropertyIndex,
490  componentIndex,
491  new AnimateToInteger( destinationValue.Get<int>() ),
492  alpha,
493  period ) );
494  break;
495  }
496 
497  case Property::FLOAT:
498  {
499  if ( ( Dali::Actor::Property::SIZE_WIDTH == targetPropertyIndex ) ||
500  ( Dali::Actor::Property::SIZE_HEIGHT == targetPropertyIndex ) ||
501  ( Dali::Actor::Property::SIZE_DEPTH == targetPropertyIndex ) )
502  {
503  // Test whether this is actually an Actor
504  Actor* maybeActor = dynamic_cast<Actor*>( &targetObject );
505  if ( maybeActor )
506  {
507  // Notify the actor that its size is being animated
508  maybeActor->NotifySizeAnimation( *this, destinationValue.Get<float>(), targetPropertyIndex );
509  }
510  }
511  else if ( ( Dali::Actor::Property::POSITION_X == targetPropertyIndex ) ||
512  ( Dali::Actor::Property::POSITION_Y == targetPropertyIndex ) ||
513  ( Dali::Actor::Property::POSITION_Z == targetPropertyIndex ) )
514  {
515  // Test whether this is actually an Actor
516  Actor* maybeActor = dynamic_cast<Actor*>( &targetObject );
517  if ( maybeActor )
518  {
519  // Notify the actor that its position is being animated
520  maybeActor->NotifyPositionAnimation( *this, destinationValue.Get<float>(), targetPropertyIndex );
521  }
522  }
523 
525  targetPropertyIndex,
526  componentIndex,
527  new AnimateToFloat( destinationValue.Get<float>() ),
528  alpha,
529  period ) );
530  break;
531  }
532 
533  case Property::VECTOR2:
534  {
536  targetPropertyIndex,
537  componentIndex,
538  new AnimateToVector2( destinationValue.Get<Vector2>() ),
539  alpha,
540  period ) );
541  break;
542  }
543 
544  case Property::VECTOR3:
545  {
546  if ( Dali::Actor::Property::SIZE == targetPropertyIndex )
547  {
548  // Test whether this is actually an Actor
549  Actor* maybeActor = dynamic_cast<Actor*>( &targetObject );
550  if ( maybeActor )
551  {
552  // Notify the actor that its size is being animated
553  maybeActor->NotifySizeAnimation( *this, destinationValue.Get<Vector3>() );
554  }
555  }
556  else if ( Dali::Actor::Property::POSITION == targetPropertyIndex )
557  {
558  // Test whether this is actually an Actor
559  Actor* maybeActor = dynamic_cast<Actor*>( &targetObject );
560  if ( maybeActor )
561  {
562  // Notify the actor that its position is being animated
563  maybeActor->NotifyPositionAnimation( *this, destinationValue.Get<Vector3>() );
564  }
565  }
566 
568  targetPropertyIndex,
569  componentIndex,
570  new AnimateToVector3( destinationValue.Get<Vector3>() ),
571  alpha,
572  period ) );
573  break;
574  }
575 
576  case Property::VECTOR4:
577  {
579  targetPropertyIndex,
580  componentIndex,
581  new AnimateToVector4( destinationValue.Get<Vector4>() ),
582  alpha,
583  period ) );
584  break;
585  }
586 
587  case Property::ROTATION:
588  {
590  targetPropertyIndex,
591  componentIndex,
592  new RotateToQuaternion( destinationValue.Get<Quaternion>() ),
593  alpha,
594  period ) );
595  break;
596  }
597 
598  default:
599  DALI_ASSERT_ALWAYS( false && "Property type enumeration out of bounds" ); // should never come here
600  break;
601  }
602 }
603 
604 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames)
605 {
607 }
608 
609 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, Interpolation interpolation )
610 {
611  AnimateBetween(target, keyFrames, mDefaultAlpha, TimePeriod(mDurationSeconds), interpolation );
612 }
613 
614 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, TimePeriod period)
615 {
616  AnimateBetween(target, keyFrames, mDefaultAlpha, period, DEFAULT_INTERPOLATION);
617 }
618 
619 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, TimePeriod period, Interpolation interpolation)
620 {
621  AnimateBetween(target, keyFrames, mDefaultAlpha, period, interpolation);
622 }
623 
624 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha)
625 {
627 }
628 
629 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, Interpolation interpolation)
630 {
631  AnimateBetween(target, keyFrames, alpha, TimePeriod(mDurationSeconds), interpolation);
632 }
633 
634 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, TimePeriod period)
635 {
636  AnimateBetween(target, keyFrames, alpha, period, DEFAULT_INTERPOLATION);
637 }
638 
639 void Animation::AnimateBetween(Property target, const KeyFrames& keyFrames, AlphaFunction alpha, TimePeriod period, Interpolation interpolation)
640 {
641  Object& object = dynamic_cast<Object&>( GetImplementation(target.object) );
642 
643  ExtendDuration( period );
644 
645  switch(keyFrames.GetType())
646  {
648  {
649  const KeyFrameBoolean* kf;
650  GetSpecialization(keyFrames, kf);
653  target.propertyIndex,
654  target.componentIndex,
655  new KeyFrameBooleanFunctor(kfCopy),
656  alpha,
657  period ) );
658  break;
659  }
660 
662  {
663  const KeyFrameInteger* kf;
664  GetSpecialization(keyFrames, kf);
667  target.propertyIndex,
668  target.componentIndex,
669  new KeyFrameIntegerFunctor(kfCopy,interpolation),
670  alpha,
671  period ) );
672  break;
673  }
674 
676  {
677  const KeyFrameNumber* kf;
678  GetSpecialization(keyFrames, kf);
681  target.propertyIndex,
682  target.componentIndex,
683  new KeyFrameNumberFunctor(kfCopy,interpolation),
684  alpha,
685  period ) );
686  break;
687  }
688 
690  {
691  const KeyFrameVector2* kf;
692  GetSpecialization(keyFrames, kf);
695  target.propertyIndex,
696  target.componentIndex,
697  new KeyFrameVector2Functor(kfCopy,interpolation),
698  alpha,
699  period ) );
700  break;
701  }
702 
704  {
705  const KeyFrameVector3* kf;
706  GetSpecialization(keyFrames, kf);
709  target.propertyIndex,
710  target.componentIndex,
711  new KeyFrameVector3Functor(kfCopy,interpolation),
712  alpha,
713  period ) );
714  break;
715  }
716 
718  {
719  const KeyFrameVector4* kf;
720  GetSpecialization(keyFrames, kf);
723  target.propertyIndex,
724  target.componentIndex,
725  new KeyFrameVector4Functor(kfCopy,interpolation),
726  alpha,
727  period ) );
728  break;
729  }
730 
732  {
733  const KeyFrameQuaternion* kf;
734  GetSpecialization(keyFrames, kf);
737  target.propertyIndex,
738  target.componentIndex,
739  new KeyFrameQuaternionFunctor(kfCopy),
740  alpha,
741  period ) );
742  break;
743  }
744 
745  default: // not all property types are animateable
746  break;
747  }
748 }
749 
751 {
752  bool hasFinished(false);
753  const int playedCount(mAnimation->GetPlayedCount());
754 
755  // If the play count has been incremented, then another notification is required
757 
758  if (playedCount > mNotificationCount)
759  {
760  // Note that only one signal is emitted, if the animation has been played repeatedly
761  mNotificationCount = playedCount;
762 
763  hasFinished = true;
764 
766  }
767 
768  return hasFinished;
769 }
770 
772 {
773  return mFinishedSignal;
774 }
775 
777 {
778  if ( !mFinishedSignal.Empty() )
779  {
780  Dali::Animation handle( this );
781  mFinishedSignal.Emit( handle );
782  }
783 
784  // This callback is used internally, to avoid the overhead of using a signal.
785  if ( mFinishedCallback )
786  {
788  }
789 }
790 
791 bool Animation::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
792 {
793  bool connected( true );
794  Animation* animation = dynamic_cast<Animation*>(object);
795 
796  if( 0 == signalName.compare( SIGNAL_FINISHED ) )
797  {
798  animation->FinishedSignal().Connect( tracker, functor );
799  }
800  else
801  {
802  // signalName does not match any signal
803  connected = false;
804  }
805 
806  return connected;
807 }
808 
809 void Animation::SetFinishedCallback( FinishedCallback callback, Object* object )
810 {
811  mFinishedCallback = callback;
812  mFinishedCallbackObject = object;
813 }
814 
816 {
817  DALI_ASSERT_DEBUG( NULL != connector );
818 
819  connector->SetParent(*this);
820 
821  mConnectors.PushBack( connector );
822 }
823 
824 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward )
825 {
826  Animate( actor, path, forward, mDefaultAlpha, TimePeriod(mDurationSeconds) );
827 }
828 
829 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, AlphaFunction alpha )
830 {
831  Animate( actor, path, forward, alpha, TimePeriod(mDurationSeconds) );
832 }
833 
834 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, TimePeriod period )
835 {
836  Animate( actor, path, forward, mDefaultAlpha, period );
837 }
838 
839 void Animation::Animate( Actor& actor, const Path& path, const Vector3& forward, AlphaFunction alpha, TimePeriod period)
840 {
841  ExtendDuration( period );
842 
843  PathPtr pathCopy = Path::Clone(path);
844 
845  //Position animation
849  new PathPositionFunctor( pathCopy ),
850  alpha,
851  period ) );
852 
853  //If forward is zero, PathRotationFunctor will always return the unit quaternion
854  if( forward != Vector3::ZERO )
855  {
856  //Rotation animation
860  new PathRotationFunctor( pathCopy, forward ),
861  alpha,
862  period ) );
863  }
864 }
865 
866 void Animation::Show(Actor& actor, float delaySeconds)
867 {
868  ExtendDuration( TimePeriod(delaySeconds, 0) );
869 
875  TimePeriod(delaySeconds, 0.0f/*immediate*/) ) );
876 }
877 
878 void Animation::Hide(Actor& actor, float delaySeconds)
879 {
880  ExtendDuration( TimePeriod(delaySeconds, 0) );
881 
887  TimePeriod(delaySeconds, 0.0f/*immediate*/) ) );
888 }
889 
890 bool Animation::DoAction( BaseObject* object, const std::string& actionName, const Property::Map& attributes )
891 {
892  bool done = false;
893  Animation* animation = dynamic_cast<Animation*>( object );
894 
895  if( animation )
896  {
897  if( 0 == actionName.compare( ACTION_PLAY ) )
898  {
899  if( Property::Value* value = attributes.Find("duration", Property::FLOAT) )
900  {
901  animation->SetDuration( value->Get<float>() );
902  }
903 
904  animation->Play();
905  done = true;
906  }
907  else if( 0 == actionName.compare( ACTION_STOP ) )
908  {
909  animation->Stop();
910  done = true;
911  }
912  else if( 0 == actionName.compare( ACTION_PAUSE ) )
913  {
914  animation->Pause();
915  done = true;
916  }
917  }
918 
919  return done;
920 }
921 
922 void Animation::SetCurrentProgress(float progress)
923 {
924  if( mAnimation && progress >= mPlayRange.x && progress <= mPlayRange.y )
925  {
926  // mAnimation is being used in a separate thread; queue a message to set the current progress
928  }
929 }
930 
932 {
933  if( mAnimation )
934  {
935  return mAnimation->GetCurrentProgress();
936  }
937 
938  return 0.0f;
939 }
940 
941 void Animation::ExtendDuration( const TimePeriod& timePeriod )
942 {
943  float duration = timePeriod.delaySeconds + timePeriod.durationSeconds;
944 
945  if( duration > mDurationSeconds )
946  {
947  SetDuration( duration );
948  }
949 }
950 
951 void Animation::SetSpeedFactor( float factor )
952 {
953  if( mAnimation )
954  {
955  mSpeedFactor = factor;
957  }
958 }
959 
961 {
962  return mSpeedFactor;
963 }
964 
965 void Animation::SetPlayRange( const Vector2& range)
966 {
967  //Make sure the range specified is between 0.0 and 1.0
968  if( range.x >= 0.0f && range.x <= 1.0f && range.y >= 0.0f && range.y <= 1.0f )
969  {
970  Vector2 orderedRange( range );
971  //If the range is not in order swap values
972  if( range.x > range.y )
973  {
974  orderedRange = Vector2(range.y, range.x);
975  }
976 
977  // Cache for public getters
978  mPlayRange = orderedRange;
979 
980  // mAnimation is being used in a separate thread; queue a message to set play range
982  }
983 }
984 
986 {
987  return mPlayRange;
988 }
989 
990 
991 } // namespace Internal
992 
993 } // namespace Dali
Dali Docs Home
Read more about Dali