Merge "Add keyboard support and animation control in SceneLoader demo" into devel...
[platform/core/uifw/dali-demo.git] / shared / dali-table-view.cpp
1 /*
2  * Copyright (c) 2021 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/control-devel.h>
25 #include <dali-toolkit/devel-api/controls/table-view/table-view.h>
26 #include <dali-toolkit/devel-api/shader-effects/alpha-discard-effect.h>
27 #include <dali-toolkit/devel-api/shader-effects/distance-field-effect.h>
28 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
29 #include <dali/devel-api/actors/actor-devel.h>
30 #include <dali/devel-api/images/distance-field.h>
31 #include <algorithm>
32
33 // INTERNAL INCLUDES
34 #include "shared/execute-process.h"
35 #include "shared/utility.h"
36 #include "shared/view.h"
37
38 using namespace Dali;
39 using namespace Dali::Toolkit;
40
41 ///////////////////////////////////////////////////////////////////////////////
42
43 namespace
44 {
45 const std::string LOGO_PATH(DEMO_IMAGE_DIR "Logo-for-demo.png");
46
47 // Keyboard focus effect constants.
48 const float   KEYBOARD_FOCUS_ANIMATION_DURATION   = 1.0f;                                                                                                     ///< The total duration of the keyboard focus animation
49 const int32_t KEYBOARD_FOCUS_ANIMATION_LOOP_COUNT = 5;                                                                                                        ///< The number of loops for the keyboard focus animation
50 const float   KEYBOARD_FOCUS_FINAL_SCALE_FLOAT    = 1.05f;                                                                                                    ///< The final scale of the focus highlight
51 const float   KEYBOARD_FOCUS_ANIMATED_SCALE_FLOAT = 1.18f;                                                                                                    ///< The scale of the focus highlight during the animation
52 const float   KEYBOARD_FOCUS_FINAL_ALPHA          = 0.7f;                                                                                                     ///< The final alpha of the focus highlight
53 const float   KEYBOARD_FOCUS_ANIMATING_ALPHA      = 0.0f;                                                                                                     ///< The alpha of the focus highlight during the animation
54 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
55 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
56 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
57 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
58
59 const float   TILE_LABEL_PADDING          = 8.0f;  ///< Border between edge of tile and the example text
60 const float   BUTTON_PRESS_ANIMATION_TIME = 0.35f; ///< Time to perform button scale effect.
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 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.
65
66 const char* const DEMO_BUILD_DATE = __DATE__ " " __TIME__;
67
68 /**
69  * Creates the background image
70  */
71 Actor CreateBackground(std::string stylename)
72 {
73   Control background = Control::New();
74   background.SetStyleName(stylename);
75   background.SetProperty(Actor::Property::NAME, "BACKGROUND");
76   background.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
77   background.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
78   background.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
79   return background;
80 }
81
82 /**
83  * Constraint to precalculate values from the scroll-view
84  * and tile positions to pass to the tile shader.
85  */
86 struct TileShaderPositionConstraint
87 {
88   TileShaderPositionConstraint(float pageWidth, float tileXOffset)
89   : mPageWidth(pageWidth),
90     mTileXOffset(tileXOffset)
91   {
92   }
93
94   void operator()(Vector3& position, const PropertyInputContainer& inputs)
95   {
96     // Set up position.x as the tiles X offset (0.0 -> 1.0).
97     position.x = mTileXOffset;
98     // Set up position.z as the linear scroll-view X offset (0.0 -> 1.0).
99     position.z = 1.0f * (-fmod(inputs[0]->GetVector2().x, mPageWidth) / mPageWidth);
100     // Set up position.y as a rectified version of the scroll-views X offset.
101     // IE. instead of 0.0 -> 1.0, it moves between 0.0 -> 0.5 -> 0.0 within the same span.
102     if(position.z > 0.5f)
103     {
104       position.y = 1.0f - position.z;
105     }
106     else
107     {
108       position.y = position.z;
109     }
110   }
111
112 private:
113   float mPageWidth;
114   float mTileXOffset;
115 };
116
117 /**
118  * Creates a popup that shows the version information of the DALi libraries and demo
119  */
120 Dali::Toolkit::Popup CreateVersionPopup(Application& application, ConnectionTrackerInterface& connectionTracker)
121 {
122   std::ostringstream stream;
123   stream << "DALi Core: " << CORE_MAJOR_VERSION << "." << CORE_MINOR_VERSION << "." << CORE_MICRO_VERSION << std::endl
124          << "(" << CORE_BUILD_DATE << ")\n";
125   stream << "DALi Adaptor: " << ADAPTOR_MAJOR_VERSION << "." << ADAPTOR_MINOR_VERSION << "." << ADAPTOR_MICRO_VERSION << std::endl
126          << "(" << ADAPTOR_BUILD_DATE << ")\n";
127   stream << "DALi Toolkit: " << TOOLKIT_MAJOR_VERSION << "." << TOOLKIT_MINOR_VERSION << "." << TOOLKIT_MICRO_VERSION << std::endl
128          << "(" << TOOLKIT_BUILD_DATE << ")\n";
129   stream << "DALi Demo:"
130          << "\n(" << DEMO_BUILD_DATE << ")\n";
131
132   Dali::Toolkit::Popup popup = Dali::Toolkit::Popup::New();
133
134   Toolkit::TextLabel titleActor = Toolkit::TextLabel::New("Version information");
135   titleActor.SetProperty(Actor::Property::NAME, "titleActor");
136   titleActor.SetProperty(Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::CENTER);
137   titleActor.SetProperty(Toolkit::TextLabel::Property::TEXT_COLOR, Color::WHITE);
138
139   Toolkit::TextLabel contentActor = Toolkit::TextLabel::New(stream.str());
140   contentActor.SetProperty(Actor::Property::NAME, "contentActor");
141   contentActor.SetProperty(Toolkit::TextLabel::Property::MULTI_LINE, true);
142   contentActor.SetProperty(Toolkit::TextLabel::Property::HORIZONTAL_ALIGNMENT, HorizontalAlignment::CENTER);
143   contentActor.SetProperty(Toolkit::TextLabel::Property::TEXT_COLOR, Color::WHITE);
144   contentActor.SetProperty(Actor::Property::PADDING, Padding(0.0f, 0.0f, 20.0f, 0.0f));
145
146   popup.SetTitle(titleActor);
147   popup.SetContent(contentActor);
148
149   popup.SetResizePolicy(ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::WIDTH);
150   popup.SetProperty(Actor::Property::SIZE_MODE_FACTOR, Vector3(0.75f, 1.0f, 1.0f));
151   popup.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::HEIGHT);
152
153   application.GetWindow().Add(popup);
154
155   // Hide the popup when touched outside
156   popup.OutsideTouchedSignal().Connect(
157     &connectionTracker,
158     [popup]() mutable {
159       if(popup && (popup.GetDisplayState() == Toolkit::Popup::SHOWN))
160       {
161         popup.SetDisplayState(Popup::HIDDEN);
162       }
163     });
164
165   return popup;
166 }
167
168 /// Sets up the inner cube effect
169 Dali::Toolkit::ScrollViewEffect SetupInnerPageCubeEffect(const Vector2 windowSize, int totalPages)
170 {
171   Dali::Path            path = Dali::Path::New();
172   Dali::Property::Array points;
173   points.Resize(3);
174   points[0] = Vector3(windowSize.x * 0.5, 0.0f, windowSize.x * 0.5f);
175   points[1] = Vector3(0.0f, 0.0f, 0.0f);
176   points[2] = Vector3(-windowSize.x * 0.5f, 0.0f, windowSize.x * 0.5f);
177   path.SetProperty(Path::Property::POINTS, points);
178
179   Dali::Property::Array controlPoints;
180   controlPoints.Resize(4);
181   controlPoints[0] = Vector3(windowSize.x * 0.5f, 0.0f, windowSize.x * 0.3f);
182   controlPoints[1] = Vector3(windowSize.x * 0.3f, 0.0f, 0.0f);
183   controlPoints[2] = Vector3(-windowSize.x * 0.3f, 0.0f, 0.0f);
184   controlPoints[3] = Vector3(-windowSize.x * 0.5f, 0.0f, windowSize.x * 0.3f);
185   path.SetProperty(Path::Property::CONTROL_POINTS, controlPoints);
186
187   return ScrollViewPagePathEffect::New(path,
188                                        Vector3(-1.0f, 0.0f, 0.0f),
189                                        Toolkit::ScrollView::Property::SCROLL_FINAL_X,
190                                        Vector3(windowSize.x * TABLE_RELATIVE_SIZE.x, windowSize.y * TABLE_RELATIVE_SIZE.y, 0.0f),
191                                        totalPages);
192 }
193
194 /// Sets up the scroll view rulers
195 void SetupScrollViewRulers(ScrollView& scrollView, const uint16_t windowWidth, const float pageWidth, const int totalPages)
196 {
197   // Update Ruler info. for the scroll-view
198   Dali::Toolkit::RulerPtr rulerX = new FixedRuler(pageWidth);
199   Dali::Toolkit::RulerPtr rulerY = new DefaultRuler();
200   rulerX->SetDomain(RulerDomain(0.0f, (totalPages + 1) * windowWidth * TABLE_RELATIVE_SIZE.x * 0.5f, true));
201   rulerY->Disable();
202   scrollView.SetRulerX(rulerX);
203   scrollView.SetRulerY(rulerY);
204 }
205
206 } // namespace
207
208 DaliTableView::DaliTableView(Application& application)
209 : mApplication(application),
210   mRootActor(),
211   mPressedAnimation(),
212   mScrollView(),
213   mScrollViewEffect(),
214   mPressedActor(),
215   mLogoTapDetector(),
216   mVersionPopup(),
217   mPages(),
218   mExampleList(),
219   mPageWidth(0.0f),
220   mTotalPages(),
221   mScrolling(false),
222   mSortAlphabetically(false)
223 {
224   application.InitSignal().Connect(this, &DaliTableView::Initialize);
225 }
226
227 void DaliTableView::AddExample(Example example)
228 {
229   mExampleList.push_back(example);
230 }
231
232 void DaliTableView::SortAlphabetically(bool sortAlphabetically)
233 {
234   mSortAlphabetically = sortAlphabetically;
235 }
236
237 void DaliTableView::Initialize(Application& application)
238 {
239   Window window = application.GetWindow();
240   window.KeyEventSignal().Connect(this, &DaliTableView::OnKeyEvent);
241   const Window::WindowSize windowSize = window.GetSize();
242
243   // Background
244   mRootActor = CreateBackground("LauncherBackground");
245   window.Add(mRootActor);
246
247   // Add logo
248   ImageView logo = ImageView::New(LOGO_PATH);
249   logo.SetProperty(Actor::Property::NAME, "LOGO_IMAGE");
250   logo.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_CENTER);
251   logo.SetProperty(Actor::Property::PARENT_ORIGIN, Vector3(0.5f, 0.1f, 0.5f));
252   logo.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS);
253   // The logo should appear on top of everything.
254   logo.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
255   mRootActor.Add(logo);
256
257   // Show version in a popup when log is tapped
258   mLogoTapDetector = TapGestureDetector::New();
259   mLogoTapDetector.Attach(logo);
260   mLogoTapDetector.DetectedSignal().Connect(this, &DaliTableView::OnLogoTapped);
261
262   // Scrollview occupying the majority of the screen
263   mScrollView = ScrollView::New();
264   mScrollView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::BOTTOM_CENTER);
265   mScrollView.SetProperty(Actor::Property::PARENT_ORIGIN, Vector3(0.5f, 1.0f - 0.05f, 0.5f));
266   mScrollView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH);
267   mScrollView.SetResizePolicy(ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::HEIGHT);
268   mScrollView.SetProperty(Actor::Property::SIZE_MODE_FACTOR, Vector3(0.0f, 0.6f, 0.0f));
269
270   const float buttonsPageMargin = (1.0f - TABLE_RELATIVE_SIZE.x) * 0.5f * windowSize.GetWidth();
271   mScrollView.SetProperty(Actor::Property::PADDING, Padding(buttonsPageMargin, buttonsPageMargin, 0.0f, 0.0f));
272
273   mScrollView.SetAxisAutoLock(true);
274   mScrollView.ScrollCompletedSignal().Connect(this, &DaliTableView::OnScrollComplete);
275   mScrollView.ScrollStartedSignal().Connect(this, &DaliTableView::OnScrollStart);
276   mScrollView.TouchedSignal().Connect(this, &DaliTableView::OnScrollTouched);
277
278   mPageWidth = windowSize.GetWidth() * TABLE_RELATIVE_SIZE.x * 0.5f;
279
280   mBubbleAnimator.Initialize(mRootActor, mScrollView);
281
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   CreateFocusEffect();
311 }
312
313 void DaliTableView::CreateFocusEffect()
314 {
315   // Hook the required signals to manage the focus.
316   auto keyboardFocusManager = KeyboardFocusManager::Get();
317   keyboardFocusManager.PreFocusChangeSignal().Connect(this, &DaliTableView::OnKeyboardPreFocusChange);
318   keyboardFocusManager.FocusedActorEnterKeySignal().Connect(this, &DaliTableView::OnFocusedActorActivated);
319
320   // Loop to create both actors for the focus highlight effect.
321   for(unsigned int i = 0; i < FOCUS_ANIMATION_ACTOR_NUMBER; ++i)
322   {
323     mFocusEffect[i].actor = ImageView::New();
324     mFocusEffect[i].actor.SetStyleName("FocusActor");
325     mFocusEffect[i].actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
326     mFocusEffect[i].actor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
327     mFocusEffect[i].actor.SetProperty(Actor::Property::INHERIT_SCALE, false);
328     mFocusEffect[i].actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_COLOR);
329
330     KeyFrames alphaKeyFrames = KeyFrames::New();
331     alphaKeyFrames.Add(0.0f, KEYBOARD_FOCUS_FINAL_ALPHA);
332     alphaKeyFrames.Add(KEYBOARD_FOCUS_MID_KEY_FRAME_TIME, KEYBOARD_FOCUS_ANIMATING_ALPHA);
333     alphaKeyFrames.Add(KEYBOARD_FOCUS_ANIMATION_DURATION, KEYBOARD_FOCUS_FINAL_ALPHA);
334
335     KeyFrames scaleKeyFrames = KeyFrames::New();
336     scaleKeyFrames.Add(0.0f, KEYBOARD_FOCUS_FINAL_SCALE);
337     scaleKeyFrames.Add(KEYBOARD_FOCUS_MID_KEY_FRAME_TIME, FOCUS_INDICATOR_ANIMATING_SCALE);
338     scaleKeyFrames.Add(KEYBOARD_FOCUS_ANIMATION_DURATION, KEYBOARD_FOCUS_FINAL_SCALE);
339
340     mFocusEffect[i].animation = Animation::New(KEYBOARD_FOCUS_ANIMATION_DURATION);
341     mFocusEffect[i].animation.AnimateBetween(Property(mFocusEffect[i].actor, Actor::Property::COLOR_ALPHA), alphaKeyFrames);
342     mFocusEffect[i].animation.AnimateBetween(Property(mFocusEffect[i].actor, Actor::Property::SCALE), scaleKeyFrames);
343
344     mFocusEffect[i].animation.SetLoopCount(KEYBOARD_FOCUS_ANIMATION_LOOP_COUNT);
345   }
346
347   // Parent the secondary effect from the primary.
348   mFocusEffect[0].actor.Add(mFocusEffect[1].actor);
349
350   keyboardFocusManager.SetFocusIndicatorActor(mFocusEffect[0].actor);
351
352   // Connect to the on & off scene signals of the indicator which represents when it is enabled & disabled respectively
353   mFocusEffect[0].actor.OnSceneSignal().Connect(this, &DaliTableView::OnFocusIndicatorEnabled);
354   mFocusEffect[0].actor.OffSceneSignal().Connect(this, &DaliTableView::OnFocusIndicatorDisabled);
355 }
356
357 void DaliTableView::OnFocusIndicatorEnabled(Actor /* actor */)
358 {
359   // Play the animation on the 1st glow object.
360   mFocusEffect[0].animation.Play();
361
362   // Stagger the animation on the 2nd glow object half way through.
363   mFocusEffect[1].animation.PlayFrom(KEYBOARD_FOCUS_ANIMATION_DURATION / 2.0f);
364 }
365
366 void DaliTableView::OnFocusIndicatorDisabled(Dali::Actor /* actor */)
367 {
368   // Stop the focus effect animations
369   for(auto i = 0u; i < FOCUS_ANIMATION_ACTOR_NUMBER; ++i)
370   {
371     mFocusEffect[i].animation.Stop();
372   }
373 }
374
375 void DaliTableView::ApplyCubeEffectToPages()
376 {
377   ScrollViewPagePathEffect effect = ScrollViewPagePathEffect::DownCast(mScrollViewEffect);
378   unsigned int             pageCount(0);
379   for(std::vector<Actor>::iterator pageIter = mPages.begin(); pageIter != mPages.end(); ++pageIter)
380   {
381     Actor page = *pageIter;
382     effect.ApplyToPage(page, pageCount++);
383   }
384 }
385
386 void DaliTableView::Populate()
387 {
388   const Window::WindowSize windowSize = mApplication.GetWindow().GetSize();
389
390   mTotalPages = (mExampleList.size() + EXAMPLES_PER_PAGE - 1) / EXAMPLES_PER_PAGE;
391
392   // Populate ScrollView.
393   if(mExampleList.size() > 0)
394   {
395     if(mSortAlphabetically)
396     {
397       sort(mExampleList.begin(), mExampleList.end(), [](auto& lhs, auto& rhs) -> bool { return lhs.title < rhs.title; });
398     }
399
400     unsigned int         exampleCount = 0;
401     ExampleListConstIter iter         = mExampleList.begin();
402
403     for(int t = 0; t < mTotalPages && iter != mExampleList.end(); t++)
404     {
405       // Create Table
406       TableView page = TableView::New(ROWS_PER_PAGE, EXAMPLES_PER_ROW);
407       page.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
408       page.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
409       page.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
410       mScrollView.Add(page);
411
412       // Calculate the number of images going across (columns) within a page, according to the screen resolution and dpi.
413       const float margin               = 2.0f;
414       const float tileParentMultiplier = 1.0f / EXAMPLES_PER_ROW;
415
416       for(int row = 0; row < ROWS_PER_PAGE && iter != mExampleList.end(); row++)
417       {
418         for(int column = 0; column < EXAMPLES_PER_ROW && iter != mExampleList.end(); column++)
419         {
420           const Example& example = (*iter);
421
422           // Calculate the tiles relative position on the page (between 0 & 1 in each dimension).
423           Vector2              position(static_cast<float>(column) / (EXAMPLES_PER_ROW - 1.0f), static_cast<float>(row) / (EXAMPLES_PER_ROW - 1.0f));
424           Actor                tile                 = CreateTile(example.name, example.title, Vector3(tileParentMultiplier, tileParentMultiplier, 1.0f), position);
425           AccessibilityManager accessibilityManager = AccessibilityManager::Get();
426           accessibilityManager.SetFocusOrder(tile, ++exampleCount);
427           accessibilityManager.SetAccessibilityAttribute(tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_LABEL, example.title);
428           accessibilityManager.SetAccessibilityAttribute(tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_TRAIT, "Tile");
429           accessibilityManager.SetAccessibilityAttribute(tile, Dali::Toolkit::AccessibilityManager::ACCESSIBILITY_HINT, "You can run this example");
430
431           tile.SetProperty(Actor::Property::PADDING, Padding(margin, margin, margin, margin));
432           page.AddChild(tile, TableView::CellPosition(row, column));
433
434           iter++;
435         }
436       }
437
438       mPages.push_back(page);
439     }
440   }
441
442   SetupScrollViewRulers(mScrollView, windowSize.GetWidth(), mPageWidth, mTotalPages);
443 }
444
445 Actor DaliTableView::CreateTile(const std::string& name, const std::string& title, const Dali::Vector3& sizeMultiplier, Vector2& position)
446 {
447   Toolkit::ImageView focusableTile = ImageView::New();
448
449   focusableTile.SetStyleName("DemoTile");
450   focusableTile.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
451   focusableTile.SetResizePolicy(ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS);
452   focusableTile.SetProperty(Actor::Property::SIZE_MODE_FACTOR, sizeMultiplier);
453   focusableTile.SetProperty(Actor::Property::NAME, name);
454
455   // Set the tile to be keyboard focusable
456   focusableTile.SetProperty(Actor::Property::KEYBOARD_FOCUSABLE, true);
457
458   // Register a property with the ImageView. This allows us to inject the scroll-view position into the shader.
459   Property::Value value         = Vector3(0.0f, 0.0f, 0.0f);
460   Property::Index propertyIndex = focusableTile.RegisterProperty("uCustomPosition", value);
461
462   // We create a constraint to perform a precalculation on the scroll-view X offset
463   // and pass it to the shader uniform, along with the tile's position.
464   Constraint shaderPosition = Constraint::New<Vector3>(focusableTile, propertyIndex, TileShaderPositionConstraint(mPageWidth, position.x));
465   shaderPosition.AddSource(Source(mScrollView, ScrollView::Property::SCROLL_POSITION));
466   shaderPosition.SetRemoveAction(Constraint::DISCARD);
467   shaderPosition.Apply();
468   //focusableTile.Add( tileContent );
469
470   // Create an ImageView for the 9-patch border around the tile.
471   ImageView borderImage = ImageView::New();
472   borderImage.SetStyleName("DemoTileBorder");
473   borderImage.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
474   borderImage.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
475   borderImage.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
476   borderImage.SetProperty(Actor::Property::OPACITY, 0.8f);
477   DevelControl::AppendAccessibilityRelation(borderImage, focusableTile, Accessibility::RelationType::CONTROLLED_BY);
478   focusableTile.Add(borderImage);
479
480   TextLabel label = TextLabel::New();
481   label.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
482   label.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
483   label.SetStyleName("LauncherLabel");
484   label.SetProperty(TextLabel::Property::MULTI_LINE, true);
485   label.SetProperty(TextLabel::Property::TEXT, title);
486   label.SetProperty(TextLabel::Property::HORIZONTAL_ALIGNMENT, "CENTER");
487   label.SetProperty(TextLabel::Property::VERTICAL_ALIGNMENT, "CENTER");
488   label.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::HEIGHT);
489
490   // Pad around the label as its size is the same as the 9-patch border. It will overlap it without padding.
491   label.SetProperty(Actor::Property::PADDING, Padding(TILE_LABEL_PADDING, TILE_LABEL_PADDING, TILE_LABEL_PADDING, TILE_LABEL_PADDING));
492   DevelControl::AppendAccessibilityRelation(label, focusableTile, Accessibility::RelationType::CONTROLLED_BY);
493   focusableTile.Add(label);
494
495   // Connect to the touch events
496   focusableTile.TouchedSignal().Connect(this, &DaliTableView::OnTilePressed);
497   focusableTile.HoveredSignal().Connect(this, &DaliTableView::OnTileHovered);
498   focusableTile.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_ROLE, Dali::Accessibility::Role::PUSH_BUTTON);
499   DevelControl::AccessibilityActivateSignal(focusableTile).Connect(this, [=]() {
500     DoTilePress(focusableTile, PointState::DOWN);
501     DoTilePress(focusableTile, PointState::UP);
502   });
503
504   return focusableTile;
505 }
506
507 bool DaliTableView::OnTilePressed(Actor actor, const TouchEvent& event)
508 {
509   return DoTilePress(actor, event.GetState(0));
510 }
511
512 bool DaliTableView::DoTilePress(Actor actor, PointState::Type pointState)
513 {
514   bool consumed = false;
515
516   if(PointState::DOWN == pointState)
517   {
518     mPressedActor = actor;
519     consumed      = true;
520   }
521
522   // A button press is only valid if the Down & Up events
523   // both occurred within the button.
524   if((PointState::UP == pointState) &&
525      (mPressedActor == actor))
526   {
527     // ignore Example button presses when scrolling or button animating.
528     if((!mScrolling) && (!mPressedAnimation))
529     {
530       std::string name = actor.GetProperty<std::string>(Dali::Actor::Property::NAME);
531       for(Example& example : mExampleList)
532       {
533         if(example.name == name)
534         {
535           // do nothing, until pressed animation finished.
536           consumed = true;
537           break;
538         }
539       }
540     }
541
542     if(consumed)
543     {
544       mPressedAnimation = Animation::New(BUTTON_PRESS_ANIMATION_TIME);
545       mPressedAnimation.SetEndAction(Animation::DISCARD);
546
547       // scale the content actor within the Tile, as to not affect the placement within the Table.
548       Actor content = actor.GetChildAt(0);
549       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));
550       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));
551
552       // Rotate button on the Y axis when pressed.
553       mPressedAnimation.AnimateBy(Property(content, Actor::Property::ORIENTATION), Quaternion(Degree(0.0f), Degree(180.0f), Degree(0.0f)));
554
555       mPressedAnimation.Play();
556       mPressedAnimation.FinishedSignal().Connect(this, &DaliTableView::OnPressedAnimationFinished);
557     }
558   }
559   return consumed;
560 }
561
562 void DaliTableView::OnPressedAnimationFinished(Dali::Animation& source)
563 {
564   mPressedAnimation.Reset();
565   if(mPressedActor)
566   {
567     std::string name = mPressedActor.GetProperty<std::string>(Dali::Actor::Property::NAME);
568
569     ExecuteProcess(name, mApplication);
570
571     mPressedActor.Reset();
572   }
573 }
574
575 void DaliTableView::OnScrollStart(const Dali::Vector2& position)
576 {
577   mScrolling = true;
578
579   mBubbleAnimator.PlayAnimation();
580 }
581
582 void DaliTableView::OnScrollComplete(const Dali::Vector2& position)
583 {
584   mScrolling = false;
585
586   // move focus to 1st item of new page
587   AccessibilityManager accessibilityManager = AccessibilityManager::Get();
588   accessibilityManager.SetCurrentFocusActor(mPages[mScrollView.GetCurrentPage()].GetChildAt(0));
589 }
590
591 bool DaliTableView::OnScrollTouched(Actor actor, const TouchEvent& event)
592 {
593   if(PointState::DOWN == event.GetState(0))
594   {
595     mPressedActor = actor;
596   }
597
598   return false;
599 }
600
601 void DaliTableView::ApplyScrollViewEffect()
602 {
603   // Remove old effect if exists.
604
605   if(mScrollViewEffect)
606   {
607     mScrollView.RemoveEffect(mScrollViewEffect);
608   }
609
610   // Just one effect for now
611   mScrollViewEffect = SetupInnerPageCubeEffect(mApplication.GetWindow().GetSize(), mTotalPages);
612
613   mScrollView.ApplyEffect(mScrollViewEffect);
614 }
615
616 void DaliTableView::OnKeyEvent(const KeyEvent& event)
617 {
618   if(event.GetState() == KeyEvent::DOWN)
619   {
620     if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
621     {
622       // If there's a Popup, Hide it if it's contributing to the display in any way (EG. transitioning in or out).
623       // Otherwise quit.
624       if(mVersionPopup && (mVersionPopup.GetDisplayState() != Toolkit::Popup::HIDDEN))
625       {
626         mVersionPopup.SetDisplayState(Popup::HIDDEN);
627       }
628       else
629       {
630         mApplication.Quit();
631       }
632     }
633   }
634 }
635
636 Dali::Actor DaliTableView::OnKeyboardPreFocusChange(Dali::Actor current, Dali::Actor proposed, Dali::Toolkit::Control::KeyboardFocus::Direction direction)
637 {
638   Actor nextFocusActor = proposed;
639
640   if(!current && !proposed)
641   {
642     // Set the initial focus to the first tile in the current page should be focused.
643     nextFocusActor = mPages[mScrollView.GetCurrentPage()].GetChildAt(0);
644   }
645   else if(!proposed)
646   {
647     // ScrollView is being focused but nothing in the current page can be focused further
648     // in the given direction. We should work out which page to scroll to next.
649     int currentPage = mScrollView.GetCurrentPage();
650     int newPage     = currentPage;
651     if(direction == Dali::Toolkit::Control::KeyboardFocus::LEFT)
652     {
653       newPage--;
654     }
655     else if(direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT)
656     {
657       newPage++;
658     }
659
660     newPage = std::max(0, std::min(mTotalPages - 1, newPage));
661     if(newPage == currentPage)
662     {
663       if(direction == Dali::Toolkit::Control::KeyboardFocus::LEFT)
664       {
665         newPage = mTotalPages - 1;
666       }
667       else if(direction == Dali::Toolkit::Control::KeyboardFocus::RIGHT)
668       {
669         newPage = 0;
670       }
671     }
672
673     // Scroll to the page in the given direction
674     mScrollView.ScrollTo(newPage);
675
676     if(direction == Dali::Toolkit::Control::KeyboardFocus::LEFT)
677     {
678       // Work out the cell position for the last tile
679       int remainingExamples = mExampleList.size() - newPage * EXAMPLES_PER_PAGE;
680       int rowPos            = (remainingExamples >= EXAMPLES_PER_PAGE) ? ROWS_PER_PAGE - 1 : ((remainingExamples % EXAMPLES_PER_PAGE + EXAMPLES_PER_ROW) / EXAMPLES_PER_ROW - 1);
681       int colPos            = remainingExamples >= EXAMPLES_PER_PAGE ? EXAMPLES_PER_ROW - 1 : (remainingExamples % EXAMPLES_PER_PAGE - rowPos * EXAMPLES_PER_ROW - 1);
682
683       // Move the focus to the last tile in the new page.
684       nextFocusActor = mPages[newPage].GetChildAt(rowPos * EXAMPLES_PER_ROW + colPos);
685     }
686     else
687     {
688       // Move the focus to the first tile in the new page.
689       nextFocusActor = mPages[newPage].GetChildAt(0);
690     }
691   }
692
693   return nextFocusActor;
694 }
695
696 void DaliTableView::OnFocusedActorActivated(Dali::Actor activatedActor)
697 {
698   if(activatedActor)
699   {
700     mPressedActor = activatedActor;
701
702     // Activate the current focused actor;
703     DoTilePress(mPressedActor, PointState::UP);
704   }
705 }
706
707 bool DaliTableView::OnTileHovered(Actor actor, const HoverEvent& event)
708 {
709   KeyboardFocusManager::Get().SetCurrentFocusActor(actor);
710   return true;
711 }
712
713 void DaliTableView::OnLogoTapped(Dali::Actor actor, const Dali::TapGesture& tap)
714 {
715   // Only show if currently fully hidden. If transitioning-out, the transition will not be interrupted.
716   if(!mVersionPopup || (mVersionPopup.GetDisplayState() == Toolkit::Popup::HIDDEN))
717   {
718     if(!mVersionPopup)
719     {
720       mVersionPopup = CreateVersionPopup(mApplication, *this);
721     }
722
723     mVersionPopup.SetDisplayState(Popup::SHOWN);
724   }
725 }