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