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