Remove variable shadowing
[platform/core/uifw/dali-demo.git] / examples / image-scaling-irregular-grid / image-scaling-irregular-grid-example.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 /**
19  * @file image-scaling-irregular-grid-example.cpp
20  * @brief Demonstrates how to use image scaling modes when loading images.
21  *
22  * If an image is going to be drawn on-screen at a lower resolution than it is
23  * stored at on-disk, the scaling feature of the image loader can be used to
24  * reduce the image to save memory, improve performance, and potentially display
25  * a better small version of the image than if the default size were loaded.
26  *
27  * The functions CreateImage and CreateImageView below show how to build an
28  * image using a scaling mode to have %Dali resize it during loading.
29  *
30  * This demo defaults to the SCALE_TO_FILL mode of ImageAttributes which makes
31  * sure that every pixel in the loaded image is filled with a source colour
32  * from the image's central region while losing the minimum number of pixels
33  * from its periphery.
34  * It is the best option for producing thumbnails of input images that have
35  * diverse aspect ratios.
36  *
37  * The other four scaling modes of dali can be cycled-through for the whole
38  * grid  using the button in the top-right of the toolbar.
39  * A single image can be cycled by clicking the image directly.
40  *
41  * @see CreateImage CreateImageView
42  */
43
44 // EXTERNAL INCLUDES
45 #include <dali-toolkit/dali-toolkit.h>
46 #include <dali-toolkit/devel-api/controls/control-devel.h>
47 #include <dali-toolkit/devel-api/controls/scroll-bar/scroll-bar.h>
48 #include <algorithm>
49 #include <chrono> // std::chrono::system_clock
50 #include <iostream>
51 #include <map>
52 #include <random> // std::default_random_engine
53
54 // INTERNAL INCLUDES
55 #include "grid-flags.h"
56 #include "shared/view.h"
57
58 using namespace Dali;
59 using namespace Dali::Toolkit;
60 using namespace Dali::Demo;
61
62 namespace
63 {
64 /** Controls the output of application logging. */
65 //#define DEBUG_PRINT_DIAGNOSTICS;
66
67 const char* BACKGROUND_IMAGE(DEMO_IMAGE_DIR "background-gradient.jpg");
68 const char* TOOLBAR_IMAGE(DEMO_IMAGE_DIR "top-bar.png");
69 const char* APPLICATION_TITLE("Image Scaling Modes");
70 const char* TOGGLE_SCALING_IMAGE(DEMO_IMAGE_DIR "icon-change.png");
71 const char* TOGGLE_SCALING_IMAGE_SELECTED(DEMO_IMAGE_DIR "icon-change-selected.png");
72
73 /** The width of the grid in whole grid cells. */
74 const unsigned GRID_WIDTH = 9;
75 /** Limit the grid to be no higher than this in units of a cell. */
76 const unsigned GRID_MAX_HEIGHT = 600;
77
78 /** The space between the edge of a grid cell and the image embedded within it. */
79 const unsigned GRID_CELL_PADDING = 4;
80
81 /** The aspect ratio of cells in the image grid. */
82 const float CELL_ASPECT_RATIO = 1.33333333333333333333f;
83
84 const Dali::FittingMode::Type DEFAULT_SCALING_MODE = Dali::FittingMode::SCALE_TO_FILL;
85
86 /** The number of times to spin an image on touching, each spin taking a second.*/
87 const float SPIN_DURATION = 1.0f;
88
89 /** The target image sizes in grid cells. */
90 const Vector2 IMAGE_SIZES[] = {
91   Vector2(1, 1),
92   Vector2(2, 1),
93   Vector2(3, 1),
94   Vector2(1, 2),
95   Vector2(1, 3),
96   Vector2(2, 3),
97   Vector2(3, 2),
98   // Large, tall configuration:
99   Vector2(GRID_WIDTH / 2, GRID_WIDTH + GRID_WIDTH / 2),
100   // Large, square-ish images to show shrink-to-fit well with wide and tall images:
101   Vector2(GRID_WIDTH / 2, GRID_WIDTH / 2.0f * CELL_ASPECT_RATIO + 0.5f),
102   Vector2(GRID_WIDTH - 2, (GRID_WIDTH - 2) * CELL_ASPECT_RATIO + 0.5f),
103 };
104 const unsigned NUM_IMAGE_SIZES = sizeof(IMAGE_SIZES) / sizeof(IMAGE_SIZES[0]);
105
106 /** Images to load into the grid. These are mostly large and non-square to
107  *  show the scaling. */
108 const char* IMAGE_PATHS[] = {
109
110   DEMO_IMAGE_DIR "dali-logo.png",
111   DEMO_IMAGE_DIR "com.samsung.dali-demo.ico",
112   DEMO_IMAGE_DIR "square_primitive_shapes.bmp",
113   DEMO_IMAGE_DIR "gallery-large-14.wbmp",
114
115   // Images that show aspect ratio changes clearly in primitive shapes:
116
117   DEMO_IMAGE_DIR "portrait_screen_primitive_shapes.gif",
118   DEMO_IMAGE_DIR "landscape_screen_primitive_shapes.gif",
119
120   // Images from other demos that are tall, wide or just large:
121
122   DEMO_IMAGE_DIR "gallery-large-1.jpg",
123   DEMO_IMAGE_DIR "gallery-large-2.jpg",
124   DEMO_IMAGE_DIR "gallery-large-3.jpg",
125   DEMO_IMAGE_DIR "gallery-large-4.jpg",
126   DEMO_IMAGE_DIR "gallery-large-5.jpg",
127   DEMO_IMAGE_DIR "gallery-large-6.jpg",
128   DEMO_IMAGE_DIR "gallery-large-7.jpg",
129   DEMO_IMAGE_DIR "gallery-large-8.jpg",
130   DEMO_IMAGE_DIR "gallery-large-9.jpg",
131   DEMO_IMAGE_DIR "gallery-large-10.jpg",
132   DEMO_IMAGE_DIR "gallery-large-11.jpg",
133   DEMO_IMAGE_DIR "gallery-large-12.jpg",
134   DEMO_IMAGE_DIR "gallery-large-13.jpg",
135   DEMO_IMAGE_DIR "gallery-large-14.jpg",
136   DEMO_IMAGE_DIR "gallery-large-15.jpg",
137   DEMO_IMAGE_DIR "gallery-large-16.jpg",
138   DEMO_IMAGE_DIR "gallery-large-17.jpg",
139   DEMO_IMAGE_DIR "gallery-large-18.jpg",
140   DEMO_IMAGE_DIR "gallery-large-19.jpg",
141   DEMO_IMAGE_DIR "gallery-large-20.jpg",
142   DEMO_IMAGE_DIR "gallery-large-21.jpg",
143
144   DEMO_IMAGE_DIR "background-1.jpg",
145   DEMO_IMAGE_DIR "background-2.jpg",
146   DEMO_IMAGE_DIR "background-3.jpg",
147   DEMO_IMAGE_DIR "background-4.jpg",
148   DEMO_IMAGE_DIR "background-5.jpg",
149   DEMO_IMAGE_DIR "background-blocks.jpg",
150   DEMO_IMAGE_DIR "background-magnifier.jpg",
151
152   DEMO_IMAGE_DIR "background-1.jpg",
153   DEMO_IMAGE_DIR "background-2.jpg",
154   DEMO_IMAGE_DIR "background-3.jpg",
155   DEMO_IMAGE_DIR "background-4.jpg",
156   DEMO_IMAGE_DIR "background-5.jpg",
157   DEMO_IMAGE_DIR "background-blocks.jpg",
158   DEMO_IMAGE_DIR "background-magnifier.jpg",
159
160   DEMO_IMAGE_DIR "book-landscape-cover-back.jpg",
161   DEMO_IMAGE_DIR "book-landscape-cover.jpg",
162   DEMO_IMAGE_DIR "book-landscape-p1.jpg",
163   DEMO_IMAGE_DIR "book-landscape-p2.jpg",
164
165   DEMO_IMAGE_DIR "book-portrait-cover.jpg",
166   DEMO_IMAGE_DIR "book-portrait-p1.jpg",
167   DEMO_IMAGE_DIR "book-portrait-p2.jpg",
168   NULL};
169 const unsigned     NUM_IMAGE_PATHS        = sizeof(IMAGE_PATHS) / sizeof(IMAGE_PATHS[0]) - 1u;
170 const unsigned int INITIAL_IMAGES_TO_LOAD = 10;
171
172 /**
173  * Creates an ImageView
174  *
175  * @param[in] filename The path of the image.
176  * @param[in] width The width of the image in pixels.
177  * @param[in] height The height of the image in pixels.
178  * @param[in] fittingMode The mode to use when scaling the image to fit the desired dimensions.
179  */
180 ImageView CreateImageView(const std::string& filename, int width, int height, Dali::FittingMode::Type fittingMode)
181 {
182   ImageView imageView = ImageView::New();
183
184   Property::Map map;
185   map[Toolkit::ImageVisual::Property::URL]            = filename;
186   map[Toolkit::ImageVisual::Property::DESIRED_WIDTH]  = width;
187   map[Toolkit::ImageVisual::Property::DESIRED_HEIGHT] = height;
188   map[Toolkit::ImageVisual::Property::FITTING_MODE]   = fittingMode;
189   imageView.SetProperty(Toolkit::ImageView::Property::IMAGE, map);
190
191   imageView.SetProperty(Dali::Actor::Property::NAME, filename);
192   imageView.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
193   imageView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
194
195   return imageView;
196 }
197
198 /** Cycle the scaling mode options. */
199 Dali::FittingMode::Type NextMode(const Dali::FittingMode::Type oldMode)
200 {
201   Dali::FittingMode::Type newMode = FittingMode::SHRINK_TO_FIT;
202   switch(oldMode)
203   {
204     case FittingMode::SHRINK_TO_FIT:
205       newMode = FittingMode::SCALE_TO_FILL;
206       break;
207     case FittingMode::SCALE_TO_FILL:
208       newMode = FittingMode::FIT_WIDTH;
209       break;
210     case FittingMode::FIT_WIDTH:
211       newMode = FittingMode::FIT_HEIGHT;
212       break;
213     case FittingMode::FIT_HEIGHT:
214       newMode = FittingMode::SHRINK_TO_FIT;
215       break;
216   }
217   return newMode;
218 }
219
220 /**
221  * Bundle an image path with the rectangle to pack it into.
222  * */
223 struct ImageConfiguration
224 {
225   ImageConfiguration(const char* const imagePath, const Vector2 dims)
226   : path(imagePath),
227     dimensions(dims)
228   {
229   }
230   const char* path;
231   Vector2     dimensions;
232 };
233
234 /**
235  * Post-layout image data.
236  */
237 struct PositionedImage
238 {
239   PositionedImage(ImageConfiguration& config, unsigned x, unsigned y, Vector2 imageGridDimensions)
240   : configuration(config),
241     cellX(x),
242     cellY(y),
243     imageGridDims(imageGridDimensions)
244   {
245   }
246
247   ImageConfiguration configuration;
248   unsigned           cellX;
249   unsigned           cellY;
250   Vector2            imageGridDims;
251 };
252
253 } // namespace
254
255 /**
256  * @brief The main class of the demo.
257  */
258 class ImageScalingIrregularGridController : public ConnectionTracker
259 {
260 public:
261   ImageScalingIrregularGridController(Application& application)
262   : mApplication(application),
263     mScrolling(false),
264     mImagesLoaded(0)
265   {
266     std::cout << "ImageScalingIrregularGridController::ImageScalingIrregularGridController" << std::endl;
267
268     // Connect to the Application's Init signal
269     mApplication.InitSignal().Connect(this, &ImageScalingIrregularGridController::Create);
270   }
271
272   ~ImageScalingIrregularGridController()
273   {
274     // Nothing to do here.
275   }
276
277   /**
278    * Called everytime an ImageView has loaded it's image
279    */
280   void ResourceReadySignal(Toolkit::Control control)
281   {
282     mImagesLoaded++;
283     // To allow fast startup, we only place a small number of ImageViews on window first
284     if(mImagesLoaded == INITIAL_IMAGES_TO_LOAD)
285     {
286       // Adding the ImageViews to the window will trigger loading of the Images
287       mGridActor.Add(mOffWindowImageViews);
288     }
289   }
290
291   /**
292    * One-time setup in response to Application InitSignal.
293    */
294   void Create(Application& application)
295   {
296     std::cout << "ImageScalingIrregularGridController::Create" << std::endl;
297
298     // Get a handle to the window:
299     Window window = application.GetWindow();
300
301     // Connect to input event signals:
302     window.KeyEventSignal().Connect(this, &ImageScalingIrregularGridController::OnKeyEvent);
303
304     // Create a default view with a default tool bar:
305     mContentLayer = DemoHelper::CreateView(mApplication,
306                                            mView,
307                                            mToolBar,
308                                            BACKGROUND_IMAGE,
309                                            TOOLBAR_IMAGE,
310                                            "");
311
312     // Create an image scaling toggle button. (right of toolbar)
313     Toolkit::PushButton toggleScalingButton = Toolkit::PushButton::New();
314     toggleScalingButton.SetProperty(Toolkit::Button::Property::UNSELECTED_BACKGROUND_VISUAL, TOGGLE_SCALING_IMAGE);
315     toggleScalingButton.SetProperty(Toolkit::Button::Property::SELECTED_BACKGROUND_VISUAL, TOGGLE_SCALING_IMAGE_SELECTED);
316     toggleScalingButton.ClickedSignal().Connect(this, &ImageScalingIrregularGridController::OnToggleScalingTouched);
317     mToolBar.AddControl(toggleScalingButton, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage, Toolkit::Alignment::HORIZONTAL_RIGHT, DemoHelper::DEFAULT_MODE_SWITCH_PADDING);
318
319     SetTitle(APPLICATION_TITLE);
320
321     mOffWindowImageViews = Actor::New();
322     mOffWindowImageViews.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
323     mOffWindowImageViews.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
324     mOffWindowImageViews.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
325
326     // Build the main content of the widow:
327     PopulateContentLayer(DEFAULT_SCALING_MODE);
328   }
329
330   /**
331    * Build the main part of the application's view.
332    */
333   void PopulateContentLayer(const Dali::FittingMode::Type fittingMode)
334   {
335     Window  window     = mApplication.GetWindow();
336     Vector2 windowSize = window.GetSize();
337
338     float fieldHeight;
339     Actor imageField = BuildImageField(windowSize.x, GRID_WIDTH, GRID_MAX_HEIGHT, fittingMode, fieldHeight);
340
341     mScrollView = ScrollView::New();
342
343     mScrollView.ScrollStartedSignal().Connect(this, &ImageScalingIrregularGridController::OnScrollStarted);
344     mScrollView.ScrollCompletedSignal().Connect(this, &ImageScalingIrregularGridController::OnScrollCompleted);
345
346     mScrollView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
347     mScrollView.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
348
349     mScrollView.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
350
351     mScrollView.SetAxisAutoLock(true);
352     mScrollView.SetAxisAutoLockGradient(1.0f);
353
354     // Restrict scrolling to mostly vertical only, but with some horizontal wiggle-room:
355
356     RulerPtr rulerX = new FixedRuler(windowSize.width);                                    //< Pull the view back to the grid's centre-line when touch is release using a snapping ruler.
357     rulerX->SetDomain(RulerDomain(windowSize.width * -0.125f, windowSize.width * 1.125f)); //< Scroll slightly left/right of image field.
358     mScrollView.SetRulerX(rulerX);
359
360     RulerPtr rulerY = new DefaultRuler(); //< Snap in multiples of a screen / window height
361     rulerY->SetDomain(RulerDomain(-fieldHeight * 0.5f + windowSize.height * 0.5f - GRID_CELL_PADDING, fieldHeight * 0.5f + windowSize.height * 0.5f + GRID_CELL_PADDING));
362     mScrollView.SetRulerY(rulerY);
363
364     mContentLayer.Add(mScrollView);
365     mScrollView.Add(imageField);
366     mGridActor = imageField;
367
368     // Create the scroll bar
369     mScrollBarVertical = ScrollBar::New(Toolkit::ScrollBar::VERTICAL);
370     mScrollBarVertical.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_RIGHT);
371     mScrollBarVertical.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_RIGHT);
372     mScrollBarVertical.SetResizePolicy(Dali::ResizePolicy::FILL_TO_PARENT, Dali::Dimension::HEIGHT);
373     mScrollBarVertical.SetResizePolicy(Dali::ResizePolicy::FIT_TO_CHILDREN, Dali::Dimension::WIDTH);
374     mScrollView.Add(mScrollBarVertical);
375
376     mScrollBarHorizontal = ScrollBar::New(Toolkit::ScrollBar::HORIZONTAL);
377     mScrollBarHorizontal.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::BOTTOM_LEFT);
378     mScrollBarHorizontal.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
379     mScrollBarHorizontal.SetResizePolicy(Dali::ResizePolicy::FIT_TO_CHILDREN, Dali::Dimension::WIDTH);
380     mScrollBarHorizontal.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(1.5f * Math::PI), Vector3::ZAXIS)));
381     mScrollView.Add(mScrollBarHorizontal);
382
383     mScrollView.OnRelayoutSignal().Connect(this, &ImageScalingIrregularGridController::OnScrollViewRelayout);
384
385     // Scroll to top of grid so first images loaded are on-screen:
386     mScrollView.ScrollTo(Vector2(0, -1000000));
387   }
388
389   void OnScrollViewRelayout(Actor actor)
390   {
391     // Make the height of the horizontal scroll bar to be the same as the width of scroll view.
392     mScrollBarHorizontal.SetProperty(Actor::Property::SIZE, Vector2(0.0f, mScrollView.GetRelayoutSize(Dimension::WIDTH)));
393   }
394
395   /**
396    * Build a field of images scaled into a variety of shapes from very wide,
397    * through square, to very tall. The images are direct children of the Dali::Actor
398    * returned.
399    **/
400   Actor BuildImageField(const float             fieldWidth,
401                         const unsigned          gridWidth,
402                         const unsigned          maxGridHeight,
403                         Dali::FittingMode::Type fittingMode,
404                         float&                  outFieldHeight)
405   {
406     // Generate the list of image configurations to be fitted into the field:
407
408     std::vector<ImageConfiguration> configurations;
409     configurations.reserve(NUM_IMAGE_PATHS * NUM_IMAGE_SIZES);
410     for(unsigned imageIndex = 0; imageIndex < NUM_IMAGE_PATHS; ++imageIndex)
411     {
412       for(unsigned dimensionsIndex = 0; dimensionsIndex < NUM_IMAGE_SIZES; ++dimensionsIndex)
413       {
414         configurations.push_back(ImageConfiguration(IMAGE_PATHS[imageIndex], IMAGE_SIZES[dimensionsIndex]));
415       }
416     }
417     // Stir-up the list to get some nice irregularity in the generated field:
418     unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count();
419     std::shuffle(configurations.begin(), configurations.end(), std::default_random_engine(seed));
420     seed = std::chrono::system_clock::now().time_since_epoch().count();
421     std::shuffle(configurations.begin(), configurations.end(), std::default_random_engine(seed));
422
423     // Place the images in the grid:
424
425     GridFlags                    grid(gridWidth, maxGridHeight);
426     std::vector<PositionedImage> placedImages;
427
428     for(std::vector<ImageConfiguration>::iterator config = configurations.begin(), end = configurations.end(); config != end; ++config)
429     {
430       unsigned cellX, cellY;
431       Vector2  imageGridDims;
432
433       // Allocate a region of the grid for the image:
434       bool allocated = grid.AllocateRegion(config->dimensions, cellX, cellY, imageGridDims);
435       if(!allocated)
436       {
437 #ifdef DEBUG_PRINT_DIAGNOSTICS
438         fprintf(stderr, "Failed to allocate image in grid with dims (%f, %f) and path: %s.\n", config->dimensions.x, config->dimensions.y, config->path);
439 #endif
440         continue;
441       }
442
443       placedImages.push_back(PositionedImage(*config, cellX, cellY, imageGridDims));
444     }
445     DALI_ASSERT_DEBUG(grid.DebugCheckGridValid() && "Cells were set more than once, indicating erroneous overlap in placing images on the grid.");
446     const unsigned actualGridHeight = grid.GetHighestUsedRow() + 1;
447
448     // Take the images images in the grid and turn their logical locations into
449     // coordinates in a frame defined by a parent actor:
450
451     Actor gridActor = Actor::New();
452     gridActor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
453     gridActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
454     gridActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
455
456     // Work out the constants of the grid and cell dimensions and positions:
457     const float   cellWidth  = fieldWidth / gridWidth;
458     const float   cellHeight = cellWidth / CELL_ASPECT_RATIO;
459     const Vector2 cellSize   = Vector2(cellWidth, cellHeight);
460     outFieldHeight           = actualGridHeight * cellHeight;
461     const Vector2 gridOrigin = Vector2(-fieldWidth * 0.5f, -outFieldHeight * 0.5);
462
463     unsigned int count = 0;
464     // Build the image actors in their right locations in their parent's frame:
465     for(std::vector<PositionedImage>::const_iterator i = placedImages.begin(), end = placedImages.end(); i != end; ++i, ++count)
466     {
467       const PositionedImage& imageSource       = *i;
468       const Vector2          imageSize         = imageSource.imageGridDims * cellSize - Vector2(GRID_CELL_PADDING * 2, GRID_CELL_PADDING * 2);
469       const Vector2          imageRegionCorner = gridOrigin + cellSize * Vector2(imageSource.cellX, imageSource.cellY);
470       const Vector2          imagePosition     = imageRegionCorner + Vector2(GRID_CELL_PADDING, GRID_CELL_PADDING) + imageSize * 0.5f;
471
472       ImageView image = CreateImageView(imageSource.configuration.path, imageSize.x, imageSize.y, fittingMode);
473       image.SetProperty(Actor::Property::POSITION, Vector3(imagePosition.x, imagePosition.y, 0));
474       image.SetProperty(Actor::Property::SIZE, imageSize);
475       image.TouchedSignal().Connect(this, &ImageScalingIrregularGridController::OnTouchImage);
476       image.ResourceReadySignal().Connect(this, &ImageScalingIrregularGridController::ResourceReadySignal);
477       mFittingModes[image.GetProperty<int>(Actor::Property::ID)] = fittingMode;
478       mResourceUrls[image.GetProperty<int>(Actor::Property::ID)] = imageSource.configuration.path;
479       mSizes[image.GetProperty<int>(Actor::Property::ID)]        = imageSize;
480       if(count < INITIAL_IMAGES_TO_LOAD)
481       {
482         gridActor.Add(image);
483       }
484       else
485       {
486         // Store the ImageView in an offwindow actor until the inital batch of ImageViews have finished loading their images
487         // Required
488         mOffWindowImageViews.Add(image);
489       }
490     }
491
492     return gridActor;
493   }
494
495   /**
496   * Upon Touching an image (Release), change its scaling mode and make it spin, provided we're not scrolling.
497   * @param[in] actor The actor touched
498   * @param[in] event The Touch information.
499   */
500   bool OnTouchImage(Actor actor, const TouchEvent& event)
501   {
502     if((event.GetPointCount() > 0) && (!mScrolling))
503     {
504       if(event.GetState(0) == PointState::UP)
505       {
506         // Spin the image a few times:
507         Animation animation = Animation::New(SPIN_DURATION);
508         animation.AnimateBy(Property(actor, Actor::Property::ORIENTATION), Quaternion(Radian(Degree(360.0f * SPIN_DURATION)), Vector3::XAXIS), AlphaFunction::EASE_OUT);
509         animation.Play();
510
511         // Change the scaling mode:
512         const unsigned          id        = actor.GetProperty<int>(Actor::Property::ID);
513         Dali::FittingMode::Type newMode   = NextMode(mFittingModes[id]);
514         const Vector2           imageSize = mSizes[actor.GetProperty<int>(Actor::Property::ID)];
515
516         ImageView imageView = ImageView::DownCast(actor);
517         if(imageView)
518         {
519           Property::Map map;
520           map[Visual::Property::TYPE]                = Visual::IMAGE;
521           map[ImageVisual::Property::URL]            = mResourceUrls[id];
522           map[ImageVisual::Property::DESIRED_WIDTH]  = imageSize.width + 0.5f;
523           map[ImageVisual::Property::DESIRED_HEIGHT] = imageSize.height + 0.5f;
524           map[ImageVisual::Property::FITTING_MODE]   = newMode;
525           imageView.SetProperty(ImageView::Property::IMAGE, map);
526         }
527
528         mFittingModes[id] = newMode;
529       }
530     }
531     return false;
532   }
533
534   /**
535   * Main key event handler.
536   * Quit on escape key.
537   */
538   void OnKeyEvent(const KeyEvent& event)
539   {
540     if(event.GetState() == KeyEvent::DOWN)
541     {
542       if(IsKey(event, Dali::DALI_KEY_ESCAPE) || IsKey(event, Dali::DALI_KEY_BACK))
543       {
544         mApplication.Quit();
545       }
546     }
547   }
548
549   /**
550   * Signal handler, called when the 'Scaling' button has been touched.
551   *
552   * @param[in] button The button that was pressed.
553   */
554   bool OnToggleScalingTouched(Button button)
555   {
556     const unsigned numChildren = mGridActor.GetChildCount();
557
558     for(unsigned i = 0; i < numChildren; ++i)
559     {
560       ImageView gridImageView = ImageView::DownCast(mGridActor.GetChildAt(i));
561       if(gridImageView)
562       {
563         // Cycle the scaling mode options:
564         unsigned int id = gridImageView.GetProperty<int>(Actor::Property::ID);
565
566         const Vector2           imageSize = mSizes[id];
567         Dali::FittingMode::Type newMode   = NextMode(mFittingModes[id]);
568
569         Property::Map map;
570         map[Visual::Property::TYPE]                = Visual::IMAGE;
571         map[ImageVisual::Property::URL]            = mResourceUrls[id];
572         map[ImageVisual::Property::DESIRED_WIDTH]  = imageSize.width;
573         map[ImageVisual::Property::DESIRED_HEIGHT] = imageSize.height;
574         map[ImageVisual::Property::FITTING_MODE]   = newMode;
575         gridImageView.SetProperty(ImageView::Property::IMAGE, map);
576
577         mFittingModes[id] = newMode;
578
579         SetTitle(std::string(newMode == FittingMode::SHRINK_TO_FIT ? "SHRINK_TO_FIT" : newMode == FittingMode::SCALE_TO_FILL ? "SCALE_TO_FILL"
580                                                                                      : newMode == FittingMode::FIT_WIDTH     ? "FIT_WIDTH"
581                                                                                                                              : "FIT_HEIGHT"));
582       }
583     }
584     return true;
585   }
586
587   /**
588    * Sets/Updates the title of the View
589    * @param[in] title The new title for the view.
590    */
591   void SetTitle(const std::string& title)
592   {
593     if(!mTitleActor)
594     {
595       mTitleActor = DemoHelper::CreateToolBarLabel("");
596       // Add title to the tool bar.
597       mToolBar.AddControl(mTitleActor, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarTitlePercentage, Alignment::HORIZONTAL_CENTER);
598     }
599
600     mTitleActor.SetProperty(TextLabel::Property::TEXT, title);
601   }
602
603   /**
604    * When scroll starts (i.e. user starts to drag scrollview),
605    * note this state (mScrolling = true)
606    * @param[in] position Current Scroll Position
607    */
608   void OnScrollStarted(const Vector2& position)
609   {
610     mScrolling = true;
611   }
612
613   /**
614    * When scroll starts (i.e. user stops dragging scrollview, and scrollview has snapped to destination),
615    * note this state (mScrolling = false).
616    * @param[in] position Current Scroll Position
617    */
618   void OnScrollCompleted(const Vector2& position)
619   {
620     mScrolling = false;
621   }
622
623 private:
624   Application& mApplication;
625
626   Layer                                       mContentLayer;        ///< The content layer (contains non gui chrome actors)
627   Toolkit::Control                            mView;                ///< The View instance.
628   Toolkit::ToolBar                            mToolBar;             ///< The View's Toolbar.
629   TextLabel                                   mTitleActor;          ///< The Toolbar's Title.
630   Actor                                       mGridActor;           ///< The container for the grid of images
631   Actor                                       mOffWindowImageViews; ///< ImageViews held off window until the inital batch have loaded their images
632   ScrollView                                  mScrollView;          ///< ScrollView UI Component
633   ScrollBar                                   mScrollBarVertical;
634   ScrollBar                                   mScrollBarHorizontal;
635   bool                                        mScrolling;    ///< ScrollView scrolling state (true = scrolling, false = stationary)
636   std::map<unsigned, Dali::FittingMode::Type> mFittingModes; ///< Stores the current scaling mode of each image, keyed by image actor id.
637   std::map<unsigned, std::string>             mResourceUrls; ///< Stores the url of each image, keyed by image actor id.
638   std::map<unsigned, Vector2>                 mSizes;        ///< Stores the current size of each image, keyed by image actor id.
639   unsigned int                                mImagesLoaded; ///< How many images have been loaded
640 };
641
642 int DALI_EXPORT_API main(int argc, char** argv)
643 {
644   Application                         application = Application::New(&argc, &argv, DEMO_THEME_PATH);
645   ImageScalingIrregularGridController test(application);
646   application.MainLoop();
647   return 0;
648 }