fab87c3e785491a2d59219f9a529b168d3ef7deb
[platform/core/uifw/dali-demo.git] / shared / dali-table-view.cpp
1 /*
2  * Copyright (c) 2019 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
19 #include "dali-table-view.h"
20
21 // EXTERNAL INCLUDES
22 #include <algorithm>
23 #include <dali/devel-api/actors/actor-devel.h>
24 #include <dali/devel-api/images/distance-field.h>
25 #include <dali-toolkit/devel-api/shader-effects/alpha-discard-effect.h>
26 #include <dali-toolkit/devel-api/shader-effects/distance-field-effect.h>
27 #include <dali-toolkit/dali-toolkit.h>
28 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
29
30 // INTERNAL INCLUDES
31 #include "shared/execute-process.h"
32 #include "shared/utility.h"
33 #include "shared/view.h"
34
35 using namespace Dali;
36 using namespace Dali::Toolkit;
37
38 ///////////////////////////////////////////////////////////////////////////////
39
40 namespace
41 {
42
43 const std::string LOGO_PATH( DEMO_IMAGE_DIR "Logo-for-demo.png" );
44
45 // Keyboard focus effect constants.
46 const float KEYBOARD_FOCUS_ANIMATION_DURATION = 1.0f;           ///< The total duration of the keyboard focus animation
47 const int32_t KEYBOARD_FOCUS_ANIMATION_LOOP_COUNT = 5;          ///< The number of loops for the keyboard focus animation
48 const float KEYBOARD_FOCUS_FINAL_SCALE_FLOAT = 1.05f;           ///< The final scale of the focus highlight
49 const float KEYBOARD_FOCUS_ANIMATED_SCALE_FLOAT = 1.18f;        ///< The scale of the focus highlight during the animation
50 const float KEYBOARD_FOCUS_FINAL_ALPHA = 0.7f;                  ///< The final alpha of the focus highlight
51 const float KEYBOARD_FOCUS_ANIMATING_ALPHA = 0.0f;              ///< The alpha of the focus highlight during the animation
52 const float KEYBOARD_FOCUS_FADE_PERCENTAGE = 0.16f;             ///< The duration of the fade (from translucent to the final-alpha) as a percentage of the overall animation duration
53 const Vector3 KEYBOARD_FOCUS_FINAL_SCALE( KEYBOARD_FOCUS_FINAL_SCALE_FLOAT, KEYBOARD_FOCUS_FINAL_SCALE_FLOAT, KEYBOARD_FOCUS_FINAL_SCALE_FLOAT );  ///< @see KEYBOARD_FOCUS_START_SCALE_FLOAT
54 const Vector3 FOCUS_INDICATOR_ANIMATING_SCALE( KEYBOARD_FOCUS_ANIMATED_SCALE_FLOAT, KEYBOARD_FOCUS_ANIMATED_SCALE_FLOAT, KEYBOARD_FOCUS_ANIMATED_SCALE_FLOAT );   ///< @see KEYBOARD_FOCUS_END_SCALE_FLOAT
55 const float KEYBOARD_FOCUS_MID_KEY_FRAME_TIME = KEYBOARD_FOCUS_ANIMATION_DURATION - ( KEYBOARD_FOCUS_ANIMATION_DURATION * KEYBOARD_FOCUS_FADE_PERCENTAGE ); ///< Time of the mid key-frame
56
57 const float TILE_LABEL_PADDING = 8.0f;                          ///< Border between edge of tile and the example text
58 const float BUTTON_PRESS_ANIMATION_TIME = 0.35f;                ///< Time to perform button scale effect.
59 const float ROTATE_ANIMATION_TIME = 0.5f;                       ///< Time to perform rotate effect.
60 const int MAX_PAGES = 256;                                      ///< Maximum pages (arbitrary safety limit)
61 const int EXAMPLES_PER_ROW = 3;
62 const int ROWS_PER_PAGE = 3;
63 const int EXAMPLES_PER_PAGE = EXAMPLES_PER_ROW * ROWS_PER_PAGE;
64 const float LOGO_MARGIN_RATIO = 0.1f / 0.3f;
65 const float BOTTOM_PADDING_RATIO = 0.4f / 0.9f;
66 const Vector3 SCROLLVIEW_RELATIVE_SIZE(0.9f, 1.0f, 0.8f );      ///< ScrollView's relative size to its parent
67 const Vector3 TABLE_RELATIVE_SIZE(0.95f, 0.9f, 0.8f );          ///< TableView's relative size to the entire stage. The Y value means sum of the logo and table relative heights.
68 const float STENCIL_RELATIVE_SIZE = 1.0f;
69
70 const float EFFECT_SNAP_DURATION = 0.66f;                       ///< Scroll Snap Duration for Effects
71 const float EFFECT_FLICK_DURATION = 0.5f;                       ///< Scroll Flick Duration for Effects
72 const Vector3 ANGLE_CUBE_PAGE_ROTATE(Math::PI * 0.5f, Math::PI * 0.5f, 0.0f);
73
74 const char * const BUBBLE_COLOR_STYLE_NAME[] =
75 {
76   "BubbleColor1",
77   "BubbleColor2",
78   "BubbleColor3",
79   "BubbleColor4"
80 };
81 const int NUMBER_OF_BUBBLE_COLORS( sizeof( BUBBLE_COLOR_STYLE_NAME ) / sizeof( BUBBLE_COLOR_STYLE_NAME[ 0 ] ) );
82
83 const char * const SHAPE_IMAGE_TABLE[] =
84 {
85   DEMO_IMAGE_DIR "shape-circle.png",
86   DEMO_IMAGE_DIR "shape-bubble.png"
87 };
88 const int NUMBER_OF_SHAPE_IMAGES( sizeof( SHAPE_IMAGE_TABLE ) / sizeof( SHAPE_IMAGE_TABLE[0] ) );
89
90 const int NUM_BACKGROUND_IMAGES = 18;
91 const float BACKGROUND_SPREAD_SCALE = 1.5f;
92
93 const unsigned int BACKGROUND_ANIMATION_DURATION = 15000; // 15 secs
94
95 const float BUBBLE_MIN_Z = -1.0;
96 const float BUBBLE_MAX_Z = 0.0f;
97
98 const char * const DEMO_BUILD_DATE    = __DATE__ " " __TIME__;
99
100 /**
101  * Creates the background image
102  */
103 Control CreateBackground( std::string stylename )
104 {
105   Control background = Control::New();
106   Stage::GetCurrent().Add( background );
107   background.SetStyleName( stylename );
108   background.SetProperty( Actor::Property::NAME, "BACKGROUND" );
109   background.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
110   background.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
111   background.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
112   return background;
113 }
114
115 /**
116  * Constraint to return a position for a bubble based on the scroll value and vertical wrapping
117  */
118 struct AnimateBubbleConstraint
119 {
120 public:
121   AnimateBubbleConstraint( const Vector3& initialPos, float scale )
122   : mInitialX( initialPos.x ),
123     mScale( scale )
124   {
125   }
126
127   void operator()( Vector3& position, const PropertyInputContainer& inputs )
128   {
129     const Vector3& parentSize = inputs[1]->GetVector3();
130     const Vector3& childSize = inputs[2]->GetVector3();
131
132     // Wrap bubbles vertically.
133     float range = parentSize.y + childSize.y;
134     // This performs a float mod (we don't use fmod as we want the arithmetic modulus as opposed to the remainder).
135     position.y -= range * ( floor( position.y / range ) + 0.5f );
136
137     // Bubbles X position moves parallax to horizontal
138     // panning by a scale factor unique to each bubble.
139     position.x = mInitialX + ( inputs[0]->GetVector2().x * mScale );
140   }
141
142 private:
143   float mInitialX;
144   float mScale;
145 };
146
147 /**
148  * Constraint to precalculate values from the scroll-view
149  * and tile positions to pass to the tile shader.
150  */
151 struct TileShaderPositionConstraint
152 {
153   TileShaderPositionConstraint( float pageWidth, float tileXOffset )
154   : mPageWidth( pageWidth ),
155     mTileXOffset( tileXOffset )
156   {
157   }
158
159   void operator()( Vector3& position, const PropertyInputContainer& inputs )
160   {
161     // Set up position.x as the tiles X offset (0.0 -> 1.0).
162     position.x = mTileXOffset;
163     // Set up position.z as the linear scroll-view X offset (0.0 -> 1.0).
164     position.z = 1.0f * ( -fmod( inputs[0]->GetVector2().x, mPageWidth ) / mPageWidth );
165     // Set up position.y as a rectified version of the scroll-views X offset.
166     // IE. instead of 0.0 -> 1.0, it moves between 0.0 -> 0.5 -> 0.0 within the same span.
167     if( position.z > 0.5f )
168     {
169       position.y = 1.0f - position.z;
170     }
171     else
172     {
173       position.y = position.z;
174     }
175   }
176
177 private:
178   float mPageWidth;
179   float mTileXOffset;
180 };
181
182 bool CompareByTitle( const Example& lhs, const Example& rhs )
183 {
184   return lhs.title < rhs.title;
185 }
186
187 } // namespace
188
189 DaliTableView::DaliTableView( Application& application )
190 : mApplication( application ),
191   mRootActor(),
192   mRotateAnimation(),
193   mPressedAnimation(),
194   mScrollView(),
195   mScrollViewEffect(),
196   mScrollRulerX(),
197   mScrollRulerY(),
198   mPressedActor(),
199   mAnimationTimer(),
200   mLogoTapDetector(),
201   mVersionPopup(),
202   mPages(),
203   mBackgroundAnimations(),
204   mExampleList(),
205   mPageWidth( 0.0f ),
206   mTotalPages(),
207   mScrolling( false ),
208   mSortAlphabetically( false ),
209   mBackgroundAnimsPlaying( false )
210 {
211   application.InitSignal().Connect( this, &DaliTableView::Initialize );
212 }
213
214 DaliTableView::~DaliTableView()
215 {
216 }
217
218 void DaliTableView::AddExample( Example example )
219 {
220   mExampleList.push_back( example );
221 }
222
223 void DaliTableView::SortAlphabetically( bool sortAlphabetically )
224 {
225   mSortAlphabetically = sortAlphabetically;
226 }
227
228 void DaliTableView::Initialize( Application& application )
229 {
230   Stage::GetCurrent().KeyEventSignal().Connect( this, &DaliTableView::OnKeyEvent );
231   const Vector2 stageSize = Stage::GetCurrent().GetSize();
232
233   // Background
234   mRootActor = CreateBackground( "LauncherBackground" );
235   Stage::GetCurrent().Add( mRootActor );
236
237   // Add logo
238   ImageView logo = ImageView::New( LOGO_PATH );
239   logo.SetProperty( Actor::Property::NAME, "LOGO_IMAGE" );
240   logo.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_CENTER );
241   logo.SetProperty( Actor::Property::PARENT_ORIGIN, Vector3( 0.5f, 0.1f, 0.5f ) );
242   logo.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS );
243   // The logo should appear on top of everything.
244   logo.SetProperty( Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D );
245   mRootActor.Add( logo );
246
247   // Show version in a popup when log is tapped
248   mLogoTapDetector = TapGestureDetector::New();
249   mLogoTapDetector.Attach( logo );
250   mLogoTapDetector.DetectedSignal().Connect( this, &DaliTableView::OnLogoTapped );
251
252   // Scrollview occupying the majority of the screen
253   mScrollView = ScrollView::New();
254   mScrollView.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::BOTTOM_CENTER );
255   mScrollView.SetProperty( Actor::Property::PARENT_ORIGIN, Vector3( 0.5f, 1.0f - 0.05f, 0.5f ) );
256   mScrollView.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH );
257   mScrollView.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::HEIGHT );
258   mScrollView.SetProperty( Actor::Property::SIZE_MODE_FACTOR, Vector3( 0.0f, 0.6f, 0.0f ) );
259
260   const float buttonsPageMargin = ( 1.0f - TABLE_RELATIVE_SIZE.x ) * 0.5f * stageSize.width;
261   mScrollView.SetProperty( Actor::Property::PADDING, Padding( buttonsPageMargin, buttonsPageMargin, 0.0f, 0.0f ) );
262
263   mScrollView.SetAxisAutoLock( true );
264   mScrollView.ScrollCompletedSignal().Connect( this, &DaliTableView::OnScrollComplete );
265   mScrollView.ScrollStartedSignal().Connect( this, &DaliTableView::OnScrollStart );
266   mScrollView.TouchSignal().Connect( this, &DaliTableView::OnScrollTouched );
267
268   mPageWidth = stageSize.width * TABLE_RELATIVE_SIZE.x * 0.5f;
269
270   // Populate background and bubbles - needs to be scrollViewLayer so scroll ends show
271   Actor bubbleContainer = Actor::New();
272   bubbleContainer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
273   bubbleContainer.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
274   bubbleContainer.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
275   SetupBackground( bubbleContainer );
276
277   mRootActor.Add( bubbleContainer );
278   mRootActor.Add( mScrollView );
279
280   // Add scroll view effect and setup constraints on pages
281   ApplyScrollViewEffect();
282
283   // Add pages and tiles
284   Populate();
285
286   // Remove constraints for inner cube effect
287   ApplyCubeEffectToPages();
288
289   Dali::Window winHandle = application.GetWindow();
290
291   if( stageSize.width <= stageSize.height )
292   {
293     winHandle.AddAvailableOrientation( Dali::Window::PORTRAIT );
294     winHandle.RemoveAvailableOrientation( Dali::Window::LANDSCAPE );
295     winHandle.AddAvailableOrientation( Dali::Window::PORTRAIT_INVERSE );
296     winHandle.RemoveAvailableOrientation( Dali::Window::LANDSCAPE_INVERSE );
297   }
298   else
299   {
300     winHandle.AddAvailableOrientation( Dali::Window::LANDSCAPE );
301     winHandle.RemoveAvailableOrientation( Dali::Window::PORTRAIT );
302     winHandle.AddAvailableOrientation( Dali::Window::LANDSCAPE_INVERSE );
303     winHandle.RemoveAvailableOrientation( Dali::Window::PORTRAIT_INVERSE );
304   }
305
306   // Set initial orientation
307   unsigned int degrees = 0;
308   Rotate( degrees );
309
310   winHandle.ShowIndicator( Dali::Window::INVISIBLE );
311
312   // Background animation
313   mAnimationTimer = Timer::New( BACKGROUND_ANIMATION_DURATION );
314   mAnimationTimer.TickSignal().Connect( this, &DaliTableView::PauseBackgroundAnimation );
315   mAnimationTimer.Start();
316   mBackgroundAnimsPlaying = true;
317
318   CreateFocusEffect();
319 }
320
321 void DaliTableView::CreateFocusEffect()
322 {
323   // Hook the required signals to manage the focus.
324   auto keyboardFocusManager = KeyboardFocusManager::Get();
325   keyboardFocusManager.PreFocusChangeSignal().Connect( this, &DaliTableView::OnKeyboardPreFocusChange );
326   keyboardFocusManager.FocusedActorEnterKeySignal().Connect( this, &DaliTableView::OnFocusedActorActivated );
327   AccessibilityManager::Get().FocusedActorActivatedSignal().Connect( this, &DaliTableView::OnFocusedActorActivated );
328
329   // Loop to create both actors for the focus highlight effect.
330   for( unsigned int i = 0; i < FOCUS_ANIMATION_ACTOR_NUMBER; ++i )
331   {
332     mFocusEffect[i].actor = ImageView::New();
333     mFocusEffect[i].actor.SetStyleName( "FocusActor" );
334     mFocusEffect[i].actor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
335     mFocusEffect[i].actor.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
336     mFocusEffect[i].actor.SetProperty( Actor::Property::INHERIT_SCALE, false );
337     mFocusEffect[i].actor.SetProperty( Actor::Property::COLOR_MODE, USE_OWN_COLOR );
338
339     KeyFrames alphaKeyFrames = KeyFrames::New();
340     alphaKeyFrames.Add( 0.0f, KEYBOARD_FOCUS_FINAL_ALPHA );
341     alphaKeyFrames.Add( KEYBOARD_FOCUS_MID_KEY_FRAME_TIME, KEYBOARD_FOCUS_ANIMATING_ALPHA );
342     alphaKeyFrames.Add( KEYBOARD_FOCUS_ANIMATION_DURATION, KEYBOARD_FOCUS_FINAL_ALPHA );
343
344     KeyFrames scaleKeyFrames = KeyFrames::New();
345     scaleKeyFrames.Add( 0.0f, KEYBOARD_FOCUS_FINAL_SCALE );
346     scaleKeyFrames.Add( KEYBOARD_FOCUS_MID_KEY_FRAME_TIME, FOCUS_INDICATOR_ANIMATING_SCALE );
347     scaleKeyFrames.Add( KEYBOARD_FOCUS_ANIMATION_DURATION, KEYBOARD_FOCUS_FINAL_SCALE );
348
349     mFocusEffect[i].animation = Animation::New( KEYBOARD_FOCUS_ANIMATION_DURATION );
350     mFocusEffect[i].animation.AnimateBetween( Property( mFocusEffect[i].actor, Actor::Property::COLOR_ALPHA ), alphaKeyFrames );
351     mFocusEffect[i].animation.AnimateBetween( Property( mFocusEffect[i].actor, Actor::Property::SCALE ), scaleKeyFrames );
352
353     mFocusEffect[i].animation.SetLoopCount( KEYBOARD_FOCUS_ANIMATION_LOOP_COUNT );
354   }
355
356   // Parent the secondary effect from the primary.
357   mFocusEffect[0].actor.Add( mFocusEffect[1].actor );
358
359   keyboardFocusManager.SetFocusIndicatorActor( mFocusEffect[0].actor );
360
361   // Connect to the on & off stage signals of the indicator which represents when it is enabled & disabled respectively
362   mFocusEffect[0].actor.OnStageSignal().Connect( this, &DaliTableView::OnFocusIndicatorEnabled );
363   mFocusEffect[0].actor.OffStageSignal().Connect( this, &DaliTableView::OnFocusIndicatorDisabled );
364 }
365
366 void DaliTableView::OnFocusIndicatorEnabled( Actor /* actor */ )
367 {
368   // Play the animation on the 1st glow object.
369   mFocusEffect[0].animation.Play();
370
371   // Stagger the animation on the 2nd glow object half way through.
372   mFocusEffect[1].animation.PlayFrom( KEYBOARD_FOCUS_ANIMATION_DURATION / 2.0f );
373 }
374
375 void DaliTableView::OnFocusIndicatorDisabled( Dali::Actor /* actor */ )
376 {
377   // Stop the focus effect animations
378   for( auto i = 0u; i < FOCUS_ANIMATION_ACTOR_NUMBER; ++i )
379   {
380     mFocusEffect[ i ].animation.Stop();
381   }
382 }
383
384 void DaliTableView::ApplyCubeEffectToPages()
385 {
386   ScrollViewPagePathEffect effect = ScrollViewPagePathEffect::DownCast( mScrollViewEffect );
387   unsigned int pageCount(0);
388   for( std::vector< Actor >::iterator pageIter = mPages.begin(); pageIter != mPages.end(); ++pageIter )
389   {
390     Actor page = *pageIter;
391     effect.ApplyToPage( page, pageCount++ );
392   }
393 }
394
395 void DaliTableView::OnButtonsPageRelayout( const Dali::Actor& actor )
396 {
397 }
398
399 void DaliTableView::Populate()
400 {
401   const Vector2 stageSize = Stage::GetCurrent().GetSize();
402
403   mTotalPages = ( mExampleList.size() + EXAMPLES_PER_PAGE - 1 ) / EXAMPLES_PER_PAGE;
404
405   // Populate ScrollView.
406   if( mExampleList.size() > 0 )
407   {
408     if( mSortAlphabetically )
409     {
410       sort( mExampleList.begin(), mExampleList.end(), CompareByTitle );
411     }
412
413     unsigned int exampleCount = 0;
414     ExampleListConstIter iter = mExampleList.begin();
415
416     for( int t = 0; t < mTotalPages; t++ )
417     {
418       // Create Table
419       TableView page = TableView::New( ROWS_PER_PAGE, EXAMPLES_PER_ROW );
420       page.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
421       page.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
422       page.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
423       mScrollView.Add( page );
424
425       // Calculate the number of images going across (columns) within a page, according to the screen resolution and dpi.
426       const float margin = 2.0f;
427       const float tileParentMultiplier = 1.0f / EXAMPLES_PER_ROW;
428
429       for(int row = 0; row < ROWS_PER_PAGE; row++)
430       {
431         for(int column = 0; column < EXAMPLES_PER_ROW; column++)
432         {
433           const Example& example = ( *iter );
434
435           // Calculate the tiles relative position on the page (between 0 & 1 in each dimension).
436           Vector2 position( static_cast<float>( column ) / ( EXAMPLES_PER_ROW - 1.0f ), static_cast<float>( row ) / ( EXAMPLES_PER_ROW - 1.0f ) );
437           Actor tile = CreateTile( example.name, example.title, Vector3( tileParentMultiplier, tileParentMultiplier, 1.0f ), position );
438           AccessibilityManager accessibilityManager = AccessibilityManager::Get();
439           accessibilityManager.SetFocusOrder( tile, ++exampleCount );
440           accessibilityManager.SetAccessibilityAttribute( tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_LABEL,
441                                                   example.title );
442           accessibilityManager.SetAccessibilityAttribute( tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_TRAIT, "Tile" );
443           accessibilityManager.SetAccessibilityAttribute( tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_HINT,
444                                                   "You can run this example" );
445
446           tile.SetProperty( Actor::Property::PADDING, Padding( margin, margin, margin, margin ) );
447           page.AddChild( tile, TableView::CellPosition( row, column ) );
448
449           iter++;
450
451           if( iter == mExampleList.end() )
452           {
453             break;
454           }
455         }
456
457         if( iter == mExampleList.end() )
458         {
459           break;
460         }
461       }
462
463       mPages.push_back( page );
464
465       if( iter == mExampleList.end() )
466       {
467         break;
468       }
469     }
470   }
471
472   // Update Ruler info.
473   mScrollRulerX = new FixedRuler( mPageWidth );
474   mScrollRulerY = new DefaultRuler();
475   mScrollRulerX->SetDomain( RulerDomain( 0.0f, (mTotalPages+1) * stageSize.width * TABLE_RELATIVE_SIZE.x * 0.5f, true ) );
476   mScrollRulerY->Disable();
477   mScrollView.SetRulerX( mScrollRulerX );
478   mScrollView.SetRulerY( mScrollRulerY );
479 }
480
481 void DaliTableView::Rotate( unsigned int degrees )
482 {
483   // Resize the root actor
484   Vector2 stageSize = Stage::GetCurrent().GetSize();
485   Vector3 targetSize( stageSize.x, stageSize.y, 1.0f );
486
487   if( degrees == 90 || degrees == 270 )
488   {
489     targetSize = Vector3( stageSize.y, stageSize.x, 1.0f );
490   }
491
492   if( mRotateAnimation )
493   {
494     mRotateAnimation.Stop();
495     mRotateAnimation.Clear();
496   }
497
498   mRotateAnimation = Animation::New( ROTATE_ANIMATION_TIME );
499   mRotateAnimation.AnimateTo( Property( mRootActor, Actor::Property::ORIENTATION ), Quaternion( Radian( Degree( 360 - degrees ) ), Vector3::ZAXIS ), AlphaFunction::EASE_OUT );
500   mRotateAnimation.AnimateTo( Property( mRootActor, Actor::Property::SIZE ), targetSize, AlphaFunction::EASE_OUT );
501   mRotateAnimation.Play();
502 }
503
504 Actor DaliTableView::CreateTile( const std::string& name, const std::string& title, const Dali::Vector3& sizeMultiplier, Vector2& position )
505 {
506   Toolkit::ImageView focusableTile = ImageView::New();
507
508   focusableTile.SetStyleName( "DemoTile" );
509   focusableTile.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
510   focusableTile.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
511   focusableTile.SetProperty( Actor::Property::SIZE_MODE_FACTOR, sizeMultiplier );
512   focusableTile.SetProperty( Actor::Property::NAME, name );
513
514   // Set the tile to be keyboard focusable
515   focusableTile.SetKeyboardFocusable( true );
516
517   // Register a property with the ImageView. This allows us to inject the scroll-view position into the shader.
518   Property::Value value = Vector3( 0.0f, 0.0f, 0.0f );
519   Property::Index propertyIndex = focusableTile.RegisterProperty( "uCustomPosition", value );
520
521   // We create a constraint to perform a precalculation on the scroll-view X offset
522   // and pass it to the shader uniform, along with the tile's position.
523   Constraint shaderPosition = Constraint::New < Vector3 > ( focusableTile, propertyIndex, TileShaderPositionConstraint( mPageWidth, position.x ) );
524   shaderPosition.AddSource( Source( mScrollView, ScrollView::Property::SCROLL_POSITION ) );
525   shaderPosition.SetRemoveAction( Constraint::Discard );
526   shaderPosition.Apply();
527   //focusableTile.Add( tileContent );
528
529   // Create an ImageView for the 9-patch border around the tile.
530   ImageView borderImage = ImageView::New();
531   borderImage.SetStyleName("DemoTileBorder");
532   borderImage.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
533   borderImage.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
534   borderImage.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
535   borderImage.SetProperty( DevelActor::Property::OPACITY, 0.8f );
536   focusableTile.Add( borderImage );
537
538   TextLabel label = TextLabel::New();
539   label.SetProperty( Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER );
540   label.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
541   label.SetStyleName( "LauncherLabel" );
542   label.SetProperty( TextLabel::Property::MULTI_LINE, true );
543   label.SetProperty( TextLabel::Property::TEXT, title );
544   label.SetProperty( TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER" );
545   label.SetProperty( TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER" );
546   label.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::HEIGHT );
547
548   // Pad around the label as its size is the same as the 9-patch border. It will overlap it without padding.
549   label.SetProperty( Actor::Property::PADDING, Padding( TILE_LABEL_PADDING, TILE_LABEL_PADDING, TILE_LABEL_PADDING, TILE_LABEL_PADDING ) );
550   focusableTile.Add( label );
551
552   // Connect to the touch events
553   focusableTile.TouchSignal().Connect( this, &DaliTableView::OnTilePressed );
554   focusableTile.HoveredSignal().Connect( this, &DaliTableView::OnTileHovered );
555
556   return focusableTile;
557 }
558
559 bool DaliTableView::OnTilePressed( Actor actor, const TouchData& event )
560 {
561   return DoTilePress( actor, event.GetState( 0 ) );
562 }
563
564 bool DaliTableView::DoTilePress( Actor actor, PointState::Type pointState )
565 {
566   bool consumed = false;
567
568   if( PointState::DOWN == pointState )
569   {
570     mPressedActor = actor;
571     consumed = true;
572   }
573
574   // A button press is only valid if the Down & Up events
575   // both occurred within the button.
576   if( ( PointState::UP == pointState ) &&
577       ( mPressedActor == actor ) )
578   {
579     // ignore Example button presses when scrolling or button animating.
580     if( ( !mScrolling ) && ( !mPressedAnimation ) )
581     {
582       std::string name = actor.GetProperty< std::string >( Dali::Actor::Property::NAME );
583       const ExampleListIter end = mExampleList.end();
584       for( ExampleListIter iter = mExampleList.begin(); iter != end; ++iter )
585       {
586         if( (*iter).name == name )
587         {
588           // do nothing, until pressed animation finished.
589           consumed = true;
590           break;
591         }
592       }
593     }
594
595     if( consumed )
596     {
597       mPressedAnimation = Animation::New( BUTTON_PRESS_ANIMATION_TIME );
598       mPressedAnimation.SetEndAction( Animation::Discard );
599
600       // scale the content actor within the Tile, as to not affect the placement within the Table.
601       Actor content = actor.GetChildAt(0);
602       mPressedAnimation.AnimateTo( Property( content, Actor::Property::SCALE ), Vector3( 0.7f, 0.7f, 1.0f ), AlphaFunction::EASE_IN_OUT,
603                                  TimePeriod( 0.0f, BUTTON_PRESS_ANIMATION_TIME * 0.5f ) );
604       mPressedAnimation.AnimateTo( Property( content, Actor::Property::SCALE ), Vector3::ONE, AlphaFunction::EASE_IN_OUT,
605                                  TimePeriod( BUTTON_PRESS_ANIMATION_TIME * 0.5f, BUTTON_PRESS_ANIMATION_TIME * 0.5f ) );
606
607       // Rotate button on the Y axis when pressed.
608       mPressedAnimation.AnimateBy( Property( content, Actor::Property::ORIENTATION ), Quaternion( Degree( 0.0f ), Degree( 180.0f ), Degree( 0.0f ) ) );
609
610       mPressedAnimation.Play();
611       mPressedAnimation.FinishedSignal().Connect( this, &DaliTableView::OnPressedAnimationFinished );
612     }
613   }
614   return consumed;
615 }
616
617 void DaliTableView::OnPressedAnimationFinished( Dali::Animation& source )
618 {
619   mPressedAnimation.Reset();
620   if( mPressedActor )
621   {
622     std::string name = mPressedActor.GetProperty< std::string >( Dali::Actor::Property::NAME );
623
624     ExecuteProcess( name, mApplication );
625
626     mPressedActor.Reset();
627   }
628 }
629
630 void DaliTableView::OnScrollStart( const Dali::Vector2& position )
631 {
632   mScrolling = true;
633
634   PlayAnimation();
635 }
636
637 void DaliTableView::OnScrollComplete( const Dali::Vector2& position )
638 {
639   mScrolling = false;
640
641   // move focus to 1st item of new page
642   AccessibilityManager accessibilityManager = AccessibilityManager::Get();
643   accessibilityManager.SetCurrentFocusActor(mPages[mScrollView.GetCurrentPage()].GetChildAt(0) );
644 }
645
646 bool DaliTableView::OnScrollTouched( Actor actor, const TouchData& event )
647 {
648   if( PointState::DOWN == event.GetState( 0 ) )
649   {
650     mPressedActor = actor;
651   }
652
653   return false;
654 }
655
656 void DaliTableView::ApplyScrollViewEffect()
657 {
658   // Remove old effect if exists.
659
660   if( mScrollViewEffect )
661   {
662     mScrollView.RemoveEffect( mScrollViewEffect );
663   }
664
665   // Just one effect for now
666   SetupInnerPageCubeEffect();
667
668   mScrollView.ApplyEffect( mScrollViewEffect );
669 }
670
671 void DaliTableView::SetupInnerPageCubeEffect()
672 {
673   const Vector2 stageSize = Stage::GetCurrent().GetSize();
674
675   Dali::Path path = Dali::Path::New();
676   Dali::Property::Array points;
677   points.Resize(3);
678   points[0] = Vector3( stageSize.x*0.5, 0.0f,  stageSize.x*0.5f);
679   points[1] = Vector3( 0.0f, 0.0f, 0.0f );
680   points[2] = Vector3( -stageSize.x*0.5f, 0.0f, stageSize.x*0.5f);
681   path.SetProperty( Path::Property::POINTS, points );
682
683   Dali::Property::Array controlPoints;
684   controlPoints.Resize(4);
685   controlPoints[0] = Vector3( stageSize.x*0.5f, 0.0f, stageSize.x*0.3f );
686   controlPoints[1] = Vector3( stageSize.x*0.3f, 0.0f, 0.0f );
687   controlPoints[2] = Vector3(-stageSize.x*0.3f, 0.0f, 0.0f );
688   controlPoints[3] = Vector3(-stageSize.x*0.5f, 0.0f,  stageSize.x*0.3f );
689   path.SetProperty( Path::Property::CONTROL_POINTS, controlPoints );
690
691
692   mScrollViewEffect = ScrollViewPagePathEffect::New(path,
693                                                     Vector3(-1.0f,0.0f,0.0f),
694                                                     Toolkit::ScrollView::Property::SCROLL_FINAL_X,
695                                                     Vector3(stageSize.x*TABLE_RELATIVE_SIZE.x,stageSize.y*TABLE_RELATIVE_SIZE.y,0.0f),mTotalPages);
696 }
697
698 void DaliTableView::OnKeyEvent( const KeyEvent& event )
699 {
700   if( event.state == KeyEvent::Down )
701   {
702     if ( IsKey( event, Dali::DALI_KEY_ESCAPE) || IsKey( event, Dali::DALI_KEY_BACK) )
703     {
704       // If there's a Popup, Hide it if it's contributing to the display in any way (EG. transitioning in or out).
705       // Otherwise quit.
706       if ( mVersionPopup && ( mVersionPopup.GetDisplayState() != Toolkit::Popup::HIDDEN ) )
707       {
708         mVersionPopup.SetDisplayState( Popup::HIDDEN );
709       }
710       else
711       {
712         mApplication.Quit();
713       }
714     }
715   }
716 }
717
718 void DaliTableView::SetupBackground( Actor bubbleContainer )
719 {
720   // Add bubbles to the bubbleContainer.
721   // Note: The bubbleContainer is parented externally to this function.
722   AddBackgroundActors( bubbleContainer, NUM_BACKGROUND_IMAGES );
723 }
724
725 void DaliTableView::InitialiseBackgroundActors( Actor actor )
726 {
727   // Delete current animations
728   mBackgroundAnimations.clear();
729
730   // Create new animations
731   const Vector3 size = actor.GetTargetSize();
732
733   for( unsigned int i = 0, childCount = actor.GetChildCount(); i < childCount; ++i )
734   {
735     Actor child = actor.GetChildAt( i );
736
737     // Calculate a random position
738     Vector3 childPos( Random::Range( -size.x * 0.5f * BACKGROUND_SPREAD_SCALE, size.x * 0.85f * BACKGROUND_SPREAD_SCALE ),
739                       Random::Range( -size.y, size.y ),
740                       Random::Range( BUBBLE_MIN_Z, BUBBLE_MAX_Z ) );
741
742     child.SetPosition( childPos );
743
744     // Define bubble horizontal parallax and vertical wrapping
745     Constraint animConstraint = Constraint::New < Vector3 > ( child, Actor::Property::POSITION, AnimateBubbleConstraint( childPos, Random::Range( -0.85f, 0.25f ) ) );
746     animConstraint.AddSource( Source( mScrollView, ScrollView::Property::SCROLL_POSITION ) );
747     animConstraint.AddSource( Dali::ParentSource( Dali::Actor::Property::SIZE ) );
748     animConstraint.AddSource( Dali::LocalSource( Dali::Actor::Property::SIZE ) );
749     animConstraint.SetRemoveAction( Constraint::Discard );
750     animConstraint.Apply();
751
752     // Kickoff animation
753     Animation animation = Animation::New( Random::Range( 30.0f, 160.0f ) );
754     animation.AnimateBy( Property( child, Actor::Property::POSITION ), Vector3( 0.0f, -2000.0f, 0.0f ), AlphaFunction::LINEAR );
755     animation.SetLooping( true );
756     animation.Play();
757     mBackgroundAnimations.push_back( animation );
758   }
759 }
760
761 void DaliTableView::AddBackgroundActors( Actor layer, int count )
762 {
763   for( int i = 0; i < count; ++i )
764   {
765     float randSize = Random::Range( 10.0f, 400.0f );
766     int shapeType = static_cast<int>( Random::Range( 0.0f, NUMBER_OF_SHAPE_IMAGES - 1 ) + 0.5f );
767
768     ImageView dfActor = ImageView::New();
769     dfActor.SetSize( Vector2( randSize, randSize ) );
770     dfActor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
771
772     // Set the Image URL and the custom shader at the same time
773     Dali::Property::Map effect = Toolkit::CreateDistanceFieldEffect();
774     Property::Map imageMap;
775     imageMap.Add( ImageVisual::Property::URL, SHAPE_IMAGE_TABLE[ shapeType ] );
776     imageMap.Add( Toolkit::Visual::Property::SHADER, effect );
777     dfActor.SetProperty( Toolkit::ImageView::Property::IMAGE, imageMap );
778
779     dfActor.SetStyleName( BUBBLE_COLOR_STYLE_NAME[ i%NUMBER_OF_BUBBLE_COLORS ] );
780
781     layer.Add( dfActor );
782   }
783
784   // Positioning will occur when the layer is relaid out
785   layer.OnRelayoutSignal().Connect( this, &DaliTableView::InitialiseBackgroundActors );
786 }
787
788 bool DaliTableView::PauseBackgroundAnimation()
789 {
790   PauseAnimation();
791
792   return false;
793 }
794
795 void DaliTableView::PauseAnimation()
796 {
797   if( mBackgroundAnimsPlaying )
798   {
799     for( AnimationListIter animIter = mBackgroundAnimations.begin(); animIter != mBackgroundAnimations.end(); ++animIter )
800     {
801       Animation anim = *animIter;
802
803       anim.Stop();
804     }
805
806     mBackgroundAnimsPlaying = false;
807   }
808 }
809
810 void DaliTableView::PlayAnimation()
811 {
812   if ( !mBackgroundAnimsPlaying )
813   {
814     for( AnimationListIter animIter = mBackgroundAnimations.begin(); animIter != mBackgroundAnimations.end(); ++animIter )
815     {
816       Animation anim = *animIter;
817
818       anim.Play();
819     }
820
821     mBackgroundAnimsPlaying = true;
822   }
823
824   mAnimationTimer.SetInterval( BACKGROUND_ANIMATION_DURATION );
825 }
826
827 Dali::Actor DaliTableView::OnKeyboardPreFocusChange( Dali::Actor current, Dali::Actor proposed, Dali::Toolkit::Control::KeyboardFocus::Direction direction )
828 {
829   Actor nextFocusActor = proposed;
830
831   if( !current && !proposed  )
832   {
833     // Set the initial focus to the first tile in the current page should be focused.
834     nextFocusActor = mPages[mScrollView.GetCurrentPage()].GetChildAt(0);
835   }
836   else if( !proposed )
837   {
838     // ScrollView is being focused but nothing in the current page can be focused further
839     // in the given direction. We should work out which page to scroll to next.
840     int currentPage = mScrollView.GetCurrentPage();
841     int newPage = currentPage;
842     if( direction == Dali::Toolkit::Control::KeyboardFocus::LEFT )
843     {
844       newPage--;
845     }
846     else if( direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT )
847     {
848       newPage++;
849     }
850
851     newPage = std::max(0, std::min(mTotalPages - 1, newPage));
852     if( newPage == currentPage )
853     {
854       if( direction == Dali::Toolkit::Control::KeyboardFocus::LEFT )
855       {
856         newPage = mTotalPages - 1;
857       } else if( direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT )
858       {
859         newPage = 0;
860       }
861     }
862
863     // Scroll to the page in the given direction
864     mScrollView.ScrollTo(newPage);
865
866     if( direction == Dali::Toolkit::Control::KeyboardFocus::LEFT )
867     {
868       // Work out the cell position for the last tile
869       int remainingExamples = mExampleList.size() - newPage * EXAMPLES_PER_PAGE;
870       int rowPos = (remainingExamples >= EXAMPLES_PER_PAGE) ? ROWS_PER_PAGE - 1 : ( (remainingExamples % EXAMPLES_PER_PAGE + EXAMPLES_PER_ROW) / EXAMPLES_PER_ROW - 1 );
871       int colPos = remainingExamples >= EXAMPLES_PER_PAGE ? EXAMPLES_PER_ROW - 1 : ( remainingExamples % EXAMPLES_PER_PAGE - rowPos * EXAMPLES_PER_ROW - 1 );
872
873       // Move the focus to the last tile in the new page.
874       nextFocusActor = mPages[newPage].GetChildAt(rowPos * EXAMPLES_PER_ROW + colPos);
875     }
876     else
877     {
878       // Move the focus to the first tile in the new page.
879       nextFocusActor = mPages[newPage].GetChildAt(0);
880     }
881   }
882
883   return nextFocusActor;
884 }
885
886 void DaliTableView::OnFocusedActorActivated( Dali::Actor activatedActor )
887 {
888   if(activatedActor)
889   {
890     mPressedActor = activatedActor;
891
892     // Activate the current focused actor;
893     DoTilePress( mPressedActor, PointState::UP );
894   }
895 }
896
897 bool DaliTableView::OnTileHovered( Actor actor, const HoverEvent& event )
898 {
899   KeyboardFocusManager::Get().SetCurrentFocusActor( actor );
900   return true;
901 }
902
903 void DaliTableView::OnLogoTapped( Dali::Actor actor, const Dali::TapGesture& tap )
904 {
905   // Only show if currently fully hidden. If transitioning-out, the transition will not be interrupted.
906   if ( !mVersionPopup || ( mVersionPopup.GetDisplayState() == Toolkit::Popup::HIDDEN ) )
907   {
908     if ( !mVersionPopup )
909     {
910       std::ostringstream stream;
911       stream << "DALi Core: "    << CORE_MAJOR_VERSION << "." << CORE_MINOR_VERSION << "." << CORE_MICRO_VERSION << std::endl << "(" << CORE_BUILD_DATE << ")\n";
912       stream << "DALi Adaptor: " << ADAPTOR_MAJOR_VERSION << "." << ADAPTOR_MINOR_VERSION << "." << ADAPTOR_MICRO_VERSION << std::endl << "(" << ADAPTOR_BUILD_DATE << ")\n";
913       stream << "DALi Toolkit: " << TOOLKIT_MAJOR_VERSION << "." << TOOLKIT_MINOR_VERSION << "." << TOOLKIT_MICRO_VERSION << std::endl << "(" << TOOLKIT_BUILD_DATE << ")\n";
914       stream << "DALi Demo:" << "\n(" << DEMO_BUILD_DATE << ")\n";
915
916       mVersionPopup = Dali::Toolkit::Popup::New();
917
918       Toolkit::TextLabel titleActor = Toolkit::TextLabel::New( "Version information" );
919       titleActor.SetProperty( Actor::Property::NAME, "titleActor" );
920       titleActor.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::CENTER );
921       titleActor.SetProperty( Toolkit::TextLabel::Property::TEXT_COLOR, Color::WHITE );
922
923       Toolkit::TextLabel contentActor = Toolkit::TextLabel::New( stream.str() );
924       contentActor.SetProperty( Actor::Property::NAME, "contentActor" );
925       contentActor.SetProperty( Toolkit::TextLabel::Property::MULTI_LINE, true );
926       contentActor.SetProperty( Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::CENTER );
927       contentActor.SetProperty( Toolkit::TextLabel::Property::TEXT_COLOR, Color::WHITE );
928       contentActor.SetProperty( Actor::Property::PADDING, Padding( 0.0f, 0.0f, 20.0f, 0.0f ) );
929
930       mVersionPopup.SetTitle( titleActor );
931       mVersionPopup.SetContent( contentActor );
932
933       mVersionPopup.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH );
934       mVersionPopup.SetProperty( Actor::Property::SIZE_MODE_FACTOR, Vector3( 0.75f, 1.0f, 1.0f ) );
935       mVersionPopup.SetResizePolicy( ResizePolicy::FIT_TO_CHILDREN, Dimension::HEIGHT );
936
937       mVersionPopup.OutsideTouchedSignal().Connect( this, &DaliTableView::HideVersionPopup );
938       Stage::GetCurrent().Add( mVersionPopup );
939     }
940
941     mVersionPopup.SetDisplayState( Popup::SHOWN );
942   }
943 }
944
945 void DaliTableView::HideVersionPopup()
946 {
947   // Only hide if currently fully shown. If transitioning-in, the transition will not be interrupted.
948   if ( mVersionPopup && ( mVersionPopup.GetDisplayState() == Toolkit::Popup::SHOWN ) )
949   {
950     mVersionPopup.SetDisplayState( Popup::HIDDEN );
951   }
952 }