1a653a13477e6cc79e2a6635a27280dd32012b0e
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / image-view / image-view-impl.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 "image-view-impl.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/scripting/scripting.h>
23 #include <dali/public-api/object/type-registry-helper.h>
24 #include <dali/public-api/object/type-registry.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/devel-api/visuals/visual-actions-devel.h>
28 #include <dali-toolkit/devel-api/controls/control-devel.h>
29 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
30 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
31 #include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
32 #include <dali-toolkit/internal/visuals/visual-base-impl.h>
33 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
34 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
35 #include <dali-toolkit/public-api/visuals/visual-properties.h>
36
37 namespace Dali
38 {
39 namespace Toolkit
40 {
41 namespace Internal
42 {
43 namespace
44 {
45 const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
46
47 BaseHandle Create()
48 {
49   return Toolkit::ImageView::New();
50 }
51
52 // Setup properties, signals and actions using the type-registry.
53 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::ImageView, Toolkit::Control, Create);
54 DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "image", MAP, IMAGE)
55 DALI_PROPERTY_REGISTRATION(Toolkit, ImageView, "preMultipliedAlpha", BOOLEAN, PRE_MULTIPLIED_ALPHA)
56
57 DALI_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT(Toolkit, ImageView, "pixelArea", Vector4(0.f, 0.f, 1.f, 1.f), PIXEL_AREA)
58 DALI_TYPE_REGISTRATION_END()
59
60 } // anonymous namespace
61
62 using namespace Dali;
63
64 ImageView::ImageView()
65 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
66   mImageSize(),
67   mImageVisualPaddingSetByTransform(false),
68   mImageViewPixelAreaSetByFittingMode(false)
69 {
70 }
71
72 ImageView::~ImageView()
73 {
74 }
75
76 Toolkit::ImageView ImageView::New()
77 {
78   ImageView* impl = new ImageView();
79
80   Toolkit::ImageView handle = Toolkit::ImageView(*impl);
81
82   // Second-phase init of the implementation
83   // This can only be done after the CustomActor connection has been made...
84   impl->Initialize();
85
86   return handle;
87 }
88
89 /////////////////////////////////////////////////////////////
90
91 void ImageView::OnInitialize()
92 {
93   // ImageView can relayout in the OnImageReady, alternative to a signal would be to have a upcall from the Control to ImageView
94   Dali::Toolkit::Control handle(GetOwner());
95   handle.ResourceReadySignal().Connect(this, &ImageView::OnResourceReady);
96
97   DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
98     return std::unique_ptr<Dali::Accessibility::Accessible>(
99       new DevelControl::AccessibleImpl(actor, Dali::Accessibility::Role::IMAGE));
100   });
101 }
102
103 void ImageView::SetImage(const Property::Map& map)
104 {
105   // Comparing a property map is too expensive so just creating a new visual
106   mPropertyMap = map;
107   mUrl.clear();
108
109   Toolkit::Visual::Base visual = Toolkit::VisualFactory::Get().CreateVisual(mPropertyMap);
110   if(visual)
111   {
112     // Don't set mVisual until it is ready and shown. Getters will still use current visual.
113     if(!mVisual)
114     {
115       mVisual = visual;
116     }
117
118     if(!mShaderMap.Empty())
119     {
120       Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(visual);
121       visualImpl.SetCustomShader(mShaderMap);
122     }
123
124     DevelControl::RegisterVisual(*this, Toolkit::ImageView::Property::IMAGE, visual);
125   }
126   else
127   {
128     // Unregister the exsiting visual
129     DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::IMAGE);
130
131     // Trigger a size negotiation request that may be needed when unregistering a visual.
132     RelayoutRequest();
133   }
134
135   // Signal that a Relayout may be needed
136 }
137
138 void ImageView::SetImage(const std::string& url, ImageDimensions size)
139 {
140   // Don't bother comparing if we had a visual previously, just drop old visual and create new one
141   mUrl       = url;
142   mImageSize = size;
143   mPropertyMap.Clear();
144
145   // Don't set mVisual until it is ready and shown. Getters will still use current visual.
146   Toolkit::Visual::Base visual = Toolkit::VisualFactory::Get().CreateVisual(url, size);
147   if(visual)
148   {
149     if(!mVisual)
150     {
151       mVisual = visual;
152     }
153
154     if(!mShaderMap.Empty())
155     {
156       Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(visual);
157       visualImpl.SetCustomShader(mShaderMap);
158     }
159
160     DevelControl::RegisterVisual(*this, Toolkit::ImageView::Property::IMAGE, visual);
161   }
162   else
163   {
164     // Unregister the exsiting visual
165     DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::IMAGE);
166
167     // Trigger a size negotiation request that may be needed when unregistering a visual.
168     RelayoutRequest();
169   }
170
171   // Signal that a Relayout may be needed
172 }
173
174 void ImageView::EnablePreMultipliedAlpha(bool preMultipled)
175 {
176   if(mVisual)
177   {
178     Toolkit::GetImplementation(mVisual).EnablePreMultipliedAlpha(preMultipled);
179   }
180 }
181
182 bool ImageView::IsPreMultipliedAlphaEnabled() const
183 {
184   if(mVisual)
185   {
186     return Toolkit::GetImplementation(mVisual).IsPreMultipliedAlphaEnabled();
187   }
188   return false;
189 }
190
191 void ImageView::SetDepthIndex(int depthIndex)
192 {
193   if(mVisual)
194   {
195     mVisual.SetDepthIndex(depthIndex);
196   }
197 }
198
199 Vector3 ImageView::GetNaturalSize()
200 {
201   if(mVisual)
202   {
203     Vector2 rendererNaturalSize;
204     mVisual.GetNaturalSize(rendererNaturalSize);
205
206     Extents padding;
207     padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
208
209     rendererNaturalSize.width += (padding.start + padding.end);
210     rendererNaturalSize.height += (padding.top + padding.bottom);
211     return Vector3(rendererNaturalSize);
212   }
213
214   // if no visual then use Control's natural size
215   return Control::GetNaturalSize();
216 }
217
218 float ImageView::GetHeightForWidth(float width)
219 {
220   Extents padding;
221   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
222
223   if(mVisual)
224   {
225     return mVisual.GetHeightForWidth(width) + padding.top + padding.bottom;
226   }
227   else
228   {
229     return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
230   }
231 }
232
233 float ImageView::GetWidthForHeight(float height)
234 {
235   Extents padding;
236   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
237
238   if(mVisual)
239   {
240     return mVisual.GetWidthForHeight(height) + padding.start + padding.end;
241   }
242   else
243   {
244     return Control::GetWidthForHeight(height) + padding.start + padding.end;
245   }
246 }
247
248 void ImageView::OnRelayout(const Vector2& size, RelayoutContainer& container)
249 {
250   Control::OnRelayout(size, container);
251   if(mVisual)
252   {
253     Property::Map transformMap = Property::Map();
254
255     Extents padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
256
257     bool zeroPadding = (padding == Extents());
258
259     Vector2 naturalSize;
260     mVisual.GetNaturalSize(naturalSize);
261
262     Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(
263       Self().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
264     if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
265     {
266       std::swap(padding.start, padding.end);
267     }
268
269     // remove padding from the size to know how much is left for the visual
270     Vector2 finalSize   = size - Vector2(padding.start + padding.end, padding.top + padding.bottom);
271     Vector2 finalOffset = Vector2(padding.start, padding.top);
272
273     ApplyFittingMode(finalSize, naturalSize, finalOffset, zeroPadding, transformMap);
274
275     mVisual.SetTransformAndSize(transformMap, size);
276
277     // mVisual is not updated util the resource is ready in the case of visual replacement.
278     // in this case, the Property Map must be initialized so that the previous value is not reused.
279     // after mVisual is updated, the correct value will be reset.
280     Toolkit::Visual::Base visual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
281     if(visual && visual != mVisual)
282     {
283       visual.SetTransformAndSize(Property::Map(), size);
284     }
285   }
286 }
287
288 void ImageView::OnCreateTransitions(std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& sourceProperties,
289                                     std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& destinationProperties,
290                                     Dali::Toolkit::Control                                              source,
291                                     Dali::Toolkit::Control                                              destination)
292 {
293   // Retrieves image properties to be transitioned.
294   Dali::Property::Map imageSourcePropertyMap, imageDestinationPropertyMap;
295   MakeVisualTransition(imageSourcePropertyMap, imageDestinationPropertyMap, source, destination, Toolkit::ImageView::Property::IMAGE);
296   if(imageSourcePropertyMap.Count() > 0)
297   {
298     sourceProperties.push_back(std::pair<Dali::Property::Index, Dali::Property::Map>(Toolkit::ImageView::Property::IMAGE, imageSourcePropertyMap));
299     destinationProperties.push_back(std::pair<Dali::Property::Index, Dali::Property::Map>(Toolkit::ImageView::Property::IMAGE, imageDestinationPropertyMap));
300   }
301 }
302
303 void ImageView::OnUpdateVisualProperties(const std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& properties)
304 {
305   Toolkit::Visual::Base visual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
306   if(visual)
307   {
308     Dali::Toolkit::Control handle(GetOwner());
309
310     for(auto&& data : properties)
311     {
312       if(data.first == Toolkit::ImageView::Property::IMAGE)
313       {
314         DevelControl::DoAction(handle, Toolkit::ImageView::Property::IMAGE, DevelVisual::Action::UPDATE_PROPERTY, data.second);
315         break;
316       }
317     }
318   }
319 }
320
321 void ImageView::OnResourceReady(Toolkit::Control control)
322 {
323   // Visual ready so update visual attached to this ImageView, following call to RelayoutRequest will use this visual.
324   mVisual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
325   // Signal that a Relayout may be needed
326 }
327
328 void ImageView::SetTransformMapForFittingMode(Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, Visual::FittingMode fittingMode, Property::Map& transformMap)
329 {
330   switch(fittingMode)
331   {
332     case Visual::FittingMode::FIT_KEEP_ASPECT_RATIO:
333     {
334       auto availableVisualSize = finalSize;
335
336       // scale to fit the padded area
337       finalSize = naturalSize * std::min((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
338                                          (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
339
340       // calculate final offset within the padded area
341       finalOffset += (availableVisualSize - finalSize) * .5f;
342
343       // populate the transform map
344       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
345         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
346       break;
347     }
348     case Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO:
349     {
350       mImageViewPixelAreaSetByFittingMode = true;
351       auto availableVisualSize            = finalSize;
352       finalSize                           = naturalSize * std::max((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
353                                          (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
354
355       auto originalOffset = finalOffset;
356       finalOffset += (availableVisualSize - finalSize) * .5f;
357
358       float   x           = abs((availableVisualSize.width - finalSize.width) / finalSize.width) * .5f;
359       float   y           = abs((availableVisualSize.height - finalSize.height) / finalSize.height) * .5f;
360       float   widthRatio  = 1.f - abs((availableVisualSize.width - finalSize.width) / finalSize.width);
361       float   heightRatio = 1.f - abs((availableVisualSize.height - finalSize.height) / finalSize.height);
362       Vector4 pixelArea   = Vector4(x, y, widthRatio, heightRatio);
363       Self().SetProperty(Toolkit::ImageView::Property::PIXEL_AREA, pixelArea);
364
365       // populate the transform map
366       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, originalOffset)
367         .Add(Toolkit::Visual::Transform::Property::SIZE, availableVisualSize);
368       break;
369     }
370     case Visual::FittingMode::CENTER:
371     {
372       auto availableVisualSize = finalSize;
373       if(availableVisualSize.width > naturalSize.width && availableVisualSize.height > naturalSize.height)
374       {
375         finalSize = naturalSize;
376       }
377       else
378       {
379         finalSize = naturalSize * std::min((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
380                                            (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
381       }
382
383       finalOffset += (availableVisualSize - finalSize) * .5f;
384
385       // populate the transform map
386       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
387         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
388       break;
389     }
390     case Visual::FittingMode::FILL:
391     {
392       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
393         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
394       break;
395     }
396     case Visual::FittingMode::FIT_WIDTH:
397     case Visual::FittingMode::FIT_HEIGHT:
398     {
399       // This FittingMode already converted
400       break;
401     }
402   }
403 }
404
405 void ImageView::ApplyFittingMode(Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, bool zeroPadding, Property::Map& transformMap)
406 {
407   Visual::FittingMode fittingMode = Toolkit::GetImplementation(mVisual).GetFittingMode();
408
409   // Reset PIXEL_AREA after using OVER_FIT_KEEP_ASPECT_RATIO
410   if(mImageViewPixelAreaSetByFittingMode)
411   {
412     Self().SetProperty(Toolkit::ImageView::Property::PIXEL_AREA, FULL_TEXTURE_RECT);
413     mImageViewPixelAreaSetByFittingMode = false;
414   }
415
416   if((!zeroPadding) || // If padding is not zero
417      (fittingMode != Visual::FittingMode::FILL))
418   {
419     mImageVisualPaddingSetByTransform = true;
420
421     // If FittingMode use FIT_WIDTH or FIT_HEIGTH, it need to change proper fittingMode
422     if(fittingMode == Visual::FittingMode::FIT_WIDTH)
423     {
424       fittingMode = (finalSize.height / naturalSize.height) < (finalSize.width / naturalSize.width) ? Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::FIT_KEEP_ASPECT_RATIO;
425     }
426     else if(fittingMode == Visual::FittingMode::FIT_HEIGHT)
427     {
428       fittingMode = (finalSize.height / naturalSize.height) < (finalSize.width / naturalSize.width) ? Visual::FittingMode::FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO;
429     }
430
431     SetTransformMapForFittingMode(finalSize, naturalSize, finalOffset, fittingMode, transformMap);
432
433     // Set extra value for applying transformMap
434     transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
435                      Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE))
436       .Add(Toolkit::Visual::Transform::Property::ORIGIN, Toolkit::Align::TOP_BEGIN)
437       .Add(Toolkit::Visual::Transform::Property::ANCHOR_POINT, Toolkit::Align::TOP_BEGIN)
438       .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
439            Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE));
440   }
441   else if(mImageVisualPaddingSetByTransform && zeroPadding) // Reset offset to zero only if padding applied previously
442   {
443     mImageVisualPaddingSetByTransform = false;
444     // Reset the transform map
445     transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, Vector2::ZERO)
446       .Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
447            Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE))
448       .Add(Toolkit::Visual::Transform::Property::SIZE, Vector2::ONE)
449       .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
450            Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE));
451   }
452 }
453
454 ///////////////////////////////////////////////////////////
455 //
456 // Properties
457 //
458
459 void ImageView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
460 {
461   Toolkit::ImageView imageView = Toolkit::ImageView::DownCast(Dali::BaseHandle(object));
462
463   if(imageView)
464   {
465     ImageView& impl = GetImpl(imageView);
466     switch(index)
467     {
468       case Toolkit::ImageView::Property::IMAGE:
469       {
470         std::string          imageUrl;
471         const Property::Map* map;
472         if(value.Get(imageUrl))
473         {
474           impl.SetImage(imageUrl, ImageDimensions());
475         }
476         // if its not a string then get a Property::Map from the property if possible.
477         else
478         {
479           map = value.GetMap();
480           if(map)
481           {
482             Property::Value* shaderValue = map->Find(Toolkit::Visual::Property::SHADER, CUSTOM_SHADER);
483             // set image only if property map contains image information other than custom shader
484             if(map->Count() > 1u || !shaderValue)
485             {
486               impl.SetImage(*map);
487             }
488             // the property map contains only the custom shader
489             else if((map->Count() == 1u) && (shaderValue))
490             {
491               Property::Map* shaderMap = shaderValue->GetMap();
492               if(shaderMap)
493               {
494                 impl.mShaderMap = *shaderMap;
495
496                 if(!impl.mUrl.empty())
497                 {
498                   impl.SetImage(impl.mUrl, impl.mImageSize);
499                 }
500                 else if(!impl.mPropertyMap.Empty())
501                 {
502                   impl.SetImage(impl.mPropertyMap);
503                 }
504               }
505             }
506           }
507         }
508         break;
509       }
510
511       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
512       {
513         bool isPre;
514         if(value.Get(isPre))
515         {
516           impl.EnablePreMultipliedAlpha(isPre);
517         }
518         break;
519       }
520     }
521   }
522 }
523
524 Property::Value ImageView::GetProperty(BaseObject* object, Property::Index propertyIndex)
525 {
526   Property::Value value;
527
528   Toolkit::ImageView imageview = Toolkit::ImageView::DownCast(Dali::BaseHandle(object));
529
530   if(imageview)
531   {
532     ImageView& impl = GetImpl(imageview);
533     switch(propertyIndex)
534     {
535       case Toolkit::ImageView::Property::IMAGE:
536       {
537         if(!impl.mUrl.empty())
538         {
539           value = impl.mUrl;
540         }
541         else
542         {
543           Property::Map         map;
544           Toolkit::Visual::Base visual = DevelControl::GetVisual(impl, Toolkit::ImageView::Property::IMAGE);
545           if(visual)
546           {
547             visual.CreatePropertyMap(map);
548           }
549           value = map;
550         }
551         break;
552       }
553
554       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
555       {
556         value = impl.IsPreMultipliedAlphaEnabled();
557         break;
558       }
559     }
560   }
561
562   return value;
563 }
564
565 } // namespace Internal
566 } // namespace Toolkit
567 } // namespace Dali