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