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