Merge "Extending Text Styles - Adding Dashed/Double Underline" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / image-view / image-view-impl.cpp
1 /*
2  * Copyright (c) 2022 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::ControlAccessible(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::ClearImageVisual()
175 {
176   // Clear cached properties
177   mPropertyMap.Clear();
178   mUrl.clear();
179
180   // Unregister the exsiting visual
181   DevelControl::UnregisterVisual(*this, Toolkit::ImageView::Property::IMAGE);
182
183   // Trigger a size negotiation request that may be needed when unregistering a visual.
184   RelayoutRequest();
185 }
186
187 void ImageView::EnablePreMultipliedAlpha(bool preMultipled)
188 {
189   if(mVisual)
190   {
191     Toolkit::GetImplementation(mVisual).EnablePreMultipliedAlpha(preMultipled);
192   }
193 }
194
195 bool ImageView::IsPreMultipliedAlphaEnabled() const
196 {
197   if(mVisual)
198   {
199     return Toolkit::GetImplementation(mVisual).IsPreMultipliedAlphaEnabled();
200   }
201   return false;
202 }
203
204 void ImageView::SetDepthIndex(int depthIndex)
205 {
206   if(mVisual)
207   {
208     mVisual.SetDepthIndex(depthIndex);
209   }
210 }
211
212 Vector3 ImageView::GetNaturalSize()
213 {
214   if(mVisual)
215   {
216     Vector2 rendererNaturalSize;
217     mVisual.GetNaturalSize(rendererNaturalSize);
218
219     Extents padding;
220     padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
221
222     rendererNaturalSize.width += (padding.start + padding.end);
223     rendererNaturalSize.height += (padding.top + padding.bottom);
224     return Vector3(rendererNaturalSize);
225   }
226
227   // if no visual then use Control's natural size
228   return Control::GetNaturalSize();
229 }
230
231 float ImageView::GetHeightForWidth(float width)
232 {
233   Extents padding;
234   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
235
236   if(mVisual)
237   {
238     return mVisual.GetHeightForWidth(width) + padding.top + padding.bottom;
239   }
240   else
241   {
242     return Control::GetHeightForWidth(width) + padding.top + padding.bottom;
243   }
244 }
245
246 float ImageView::GetWidthForHeight(float height)
247 {
248   Extents padding;
249   padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
250
251   if(mVisual)
252   {
253     return mVisual.GetWidthForHeight(height) + padding.start + padding.end;
254   }
255   else
256   {
257     return Control::GetWidthForHeight(height) + padding.start + padding.end;
258   }
259 }
260
261 void ImageView::OnRelayout(const Vector2& size, RelayoutContainer& container)
262 {
263   Control::OnRelayout(size, container);
264   if(mVisual)
265   {
266     Property::Map transformMap = Property::Map();
267
268     Extents padding = Self().GetProperty<Extents>(Toolkit::Control::Property::PADDING);
269
270     bool zeroPadding = (padding == Extents());
271
272     Vector2 naturalSize;
273     mVisual.GetNaturalSize(naturalSize);
274
275     Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(
276       Self().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
277     if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
278     {
279       std::swap(padding.start, padding.end);
280     }
281
282     // remove padding from the size to know how much is left for the visual
283     Vector2 finalSize   = size - Vector2(padding.start + padding.end, padding.top + padding.bottom);
284     Vector2 finalOffset = Vector2(padding.start, padding.top);
285
286     ApplyFittingMode(finalSize, naturalSize, finalOffset, zeroPadding, transformMap);
287
288     mVisual.SetTransformAndSize(transformMap, size);
289
290     // mVisual is not updated util the resource is ready in the case of visual replacement.
291     // in this case, the Property Map must be initialized so that the previous value is not reused.
292     // after mVisual is updated, the correct value will be reset.
293     Toolkit::Visual::Base visual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
294     if(visual && visual != mVisual)
295     {
296       visual.SetTransformAndSize(Property::Map(), size);
297     }
298   }
299 }
300
301 void ImageView::OnCreateTransitions(std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& sourceProperties,
302                                     std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& destinationProperties,
303                                     Dali::Toolkit::Control                                              source,
304                                     Dali::Toolkit::Control                                              destination)
305 {
306   // Retrieves image properties to be transitioned.
307   Dali::Property::Map imageSourcePropertyMap, imageDestinationPropertyMap;
308   MakeVisualTransition(imageSourcePropertyMap, imageDestinationPropertyMap, source, destination, Toolkit::ImageView::Property::IMAGE);
309   if(imageSourcePropertyMap.Count() > 0)
310   {
311     sourceProperties.push_back(std::pair<Dali::Property::Index, Dali::Property::Map>(Toolkit::ImageView::Property::IMAGE, imageSourcePropertyMap));
312     destinationProperties.push_back(std::pair<Dali::Property::Index, Dali::Property::Map>(Toolkit::ImageView::Property::IMAGE, imageDestinationPropertyMap));
313   }
314 }
315
316 void ImageView::OnUpdateVisualProperties(const std::vector<std::pair<Dali::Property::Index, Dali::Property::Map>>& properties)
317 {
318   Toolkit::Visual::Base visual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
319   if(visual)
320   {
321     Dali::Toolkit::Control handle(GetOwner());
322
323     for(auto&& data : properties)
324     {
325       if(data.first == Toolkit::ImageView::Property::IMAGE)
326       {
327         DevelControl::DoAction(handle, Toolkit::ImageView::Property::IMAGE, DevelVisual::Action::UPDATE_PROPERTY, data.second);
328         break;
329       }
330     }
331   }
332 }
333
334 void ImageView::OnResourceReady(Toolkit::Control control)
335 {
336   // Visual ready so update visual attached to this ImageView, following call to RelayoutRequest will use this visual.
337   mVisual = DevelControl::GetVisual(*this, Toolkit::ImageView::Property::IMAGE);
338   // Signal that a Relayout may be needed
339 }
340
341 void ImageView::SetTransformMapForFittingMode(Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, Visual::FittingMode fittingMode, Property::Map& transformMap)
342 {
343   switch(fittingMode)
344   {
345     case Visual::FittingMode::FIT_KEEP_ASPECT_RATIO:
346     {
347       auto availableVisualSize = finalSize;
348
349       // scale to fit the padded area
350       finalSize = naturalSize * std::min((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
351                                          (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
352
353       // calculate final offset within the padded area
354       finalOffset += (availableVisualSize - finalSize) * .5f;
355
356       // populate the transform map
357       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
358         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
359       break;
360     }
361     case Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO:
362     {
363       mImageViewPixelAreaSetByFittingMode = true;
364       auto availableVisualSize            = finalSize;
365       finalSize                           = naturalSize * std::max((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
366                                          (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
367
368       auto originalOffset = finalOffset;
369       finalOffset += (availableVisualSize - finalSize) * .5f;
370
371       float   x           = abs((availableVisualSize.width - finalSize.width) / finalSize.width) * .5f;
372       float   y           = abs((availableVisualSize.height - finalSize.height) / finalSize.height) * .5f;
373       float   widthRatio  = 1.f - abs((availableVisualSize.width - finalSize.width) / finalSize.width);
374       float   heightRatio = 1.f - abs((availableVisualSize.height - finalSize.height) / finalSize.height);
375       Vector4 pixelArea   = Vector4(x, y, widthRatio, heightRatio);
376       Self().SetProperty(Toolkit::ImageView::Property::PIXEL_AREA, pixelArea);
377
378       // populate the transform map
379       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, originalOffset)
380         .Add(Toolkit::Visual::Transform::Property::SIZE, availableVisualSize);
381       break;
382     }
383     case Visual::FittingMode::CENTER:
384     {
385       auto availableVisualSize = finalSize;
386       if(availableVisualSize.width > naturalSize.width && availableVisualSize.height > naturalSize.height)
387       {
388         finalSize = naturalSize;
389       }
390       else
391       {
392         finalSize = naturalSize * std::min((naturalSize.width ? (availableVisualSize.width / naturalSize.width) : 0),
393                                            (naturalSize.height ? (availableVisualSize.height / naturalSize.height) : 0));
394       }
395
396       finalOffset += (availableVisualSize - finalSize) * .5f;
397
398       // populate the transform map
399       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
400         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
401       break;
402     }
403     case Visual::FittingMode::FILL:
404     {
405       transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
406         .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
407       break;
408     }
409     case Visual::FittingMode::FIT_WIDTH:
410     case Visual::FittingMode::FIT_HEIGHT:
411     {
412       // This FittingMode already converted
413       break;
414     }
415   }
416 }
417
418 void ImageView::ApplyFittingMode(Vector2 finalSize, Vector2 naturalSize, Vector2 finalOffset, bool zeroPadding, Property::Map& transformMap)
419 {
420   Visual::FittingMode fittingMode = Toolkit::GetImplementation(mVisual).GetFittingMode();
421
422   // Reset PIXEL_AREA after using OVER_FIT_KEEP_ASPECT_RATIO
423   if(mImageViewPixelAreaSetByFittingMode)
424   {
425     Self().SetProperty(Toolkit::ImageView::Property::PIXEL_AREA, FULL_TEXTURE_RECT);
426     mImageViewPixelAreaSetByFittingMode = false;
427   }
428
429   if((!zeroPadding) || // If padding is not zero
430      (fittingMode != Visual::FittingMode::FILL))
431   {
432     mImageVisualPaddingSetByTransform = true;
433
434     // If FittingMode use FIT_WIDTH or FIT_HEIGTH, it need to change proper fittingMode
435     if(fittingMode == Visual::FittingMode::FIT_WIDTH)
436     {
437       fittingMode = (finalSize.height / naturalSize.height) < (finalSize.width / naturalSize.width) ? Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::FIT_KEEP_ASPECT_RATIO;
438     }
439     else if(fittingMode == Visual::FittingMode::FIT_HEIGHT)
440     {
441       fittingMode = (finalSize.height / naturalSize.height) < (finalSize.width / naturalSize.width) ? Visual::FittingMode::FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO;
442     }
443
444     SetTransformMapForFittingMode(finalSize, naturalSize, finalOffset, fittingMode, transformMap);
445
446     // Set extra value for applying transformMap
447     transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
448                      Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE))
449       .Add(Toolkit::Visual::Transform::Property::ORIGIN, Toolkit::Align::TOP_BEGIN)
450       .Add(Toolkit::Visual::Transform::Property::ANCHOR_POINT, Toolkit::Align::TOP_BEGIN)
451       .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
452            Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE));
453   }
454   else if(mImageVisualPaddingSetByTransform && zeroPadding) // Reset offset to zero only if padding applied previously
455   {
456     mImageVisualPaddingSetByTransform = false;
457     // Reset the transform map
458     transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, Vector2::ZERO)
459       .Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
460            Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE))
461       .Add(Toolkit::Visual::Transform::Property::SIZE, Vector2::ONE)
462       .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
463            Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE));
464   }
465 }
466
467 ///////////////////////////////////////////////////////////
468 //
469 // Properties
470 //
471
472 void ImageView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
473 {
474   Toolkit::ImageView imageView = Toolkit::ImageView::DownCast(Dali::BaseHandle(object));
475
476   if(imageView)
477   {
478     ImageView& impl = GetImpl(imageView);
479     switch(index)
480     {
481       case Toolkit::ImageView::Property::IMAGE:
482       {
483         std::string          imageUrl;
484         const Property::Map* map;
485         if(value.Get(imageUrl))
486         {
487           impl.SetImage(imageUrl, ImageDimensions());
488         }
489         // if its not a string then get a Property::Map from the property if possible.
490         else
491         {
492           map = value.GetMap();
493           if(DALI_LIKELY(map))
494           {
495             // the property map is emtpy map. Unregister visual.
496             if(DALI_UNLIKELY(map->Count() == 0u))
497             {
498               impl.ClearImageVisual();
499             }
500             else
501             {
502               Property::Value* shaderValue = map->Find(Toolkit::Visual::Property::SHADER, CUSTOM_SHADER);
503               // set image only if property map contains image information other than custom shader
504               if(map->Count() > 1u || !shaderValue)
505               {
506                 impl.SetImage(*map);
507               }
508               // the property map contains only the custom shader
509               else if((map->Count() == 1u) && (shaderValue))
510               {
511                 Property::Map* shaderMap = shaderValue->GetMap();
512                 if(shaderMap)
513                 {
514                   impl.mShaderMap = *shaderMap;
515
516                   if(!impl.mUrl.empty())
517                   {
518                     impl.SetImage(impl.mUrl, impl.mImageSize);
519                   }
520                   else if(!impl.mPropertyMap.Empty())
521                   {
522                     impl.SetImage(impl.mPropertyMap);
523                   }
524                 }
525               }
526             }
527           }
528           else
529           {
530             // invalid property value comes. Unregister visual.
531             impl.ClearImageVisual();
532           }
533         }
534         break;
535       }
536
537       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
538       {
539         bool isPre;
540         if(value.Get(isPre))
541         {
542           impl.EnablePreMultipliedAlpha(isPre);
543         }
544         break;
545       }
546     }
547   }
548 }
549
550 Property::Value ImageView::GetProperty(BaseObject* object, Property::Index propertyIndex)
551 {
552   Property::Value value;
553
554   Toolkit::ImageView imageview = Toolkit::ImageView::DownCast(Dali::BaseHandle(object));
555
556   if(imageview)
557   {
558     ImageView& impl = GetImpl(imageview);
559     switch(propertyIndex)
560     {
561       case Toolkit::ImageView::Property::IMAGE:
562       {
563         if(!impl.mUrl.empty())
564         {
565           value = impl.mUrl;
566         }
567         else
568         {
569           Property::Map         map;
570           Toolkit::Visual::Base visual = DevelControl::GetVisual(impl, Toolkit::ImageView::Property::IMAGE);
571           if(visual)
572           {
573             visual.CreatePropertyMap(map);
574           }
575           value = map;
576         }
577         break;
578       }
579
580       case Toolkit::ImageView::Property::PRE_MULTIPLIED_ALPHA:
581       {
582         value = impl.IsPreMultipliedAlphaEnabled();
583         break;
584       }
585     }
586   }
587
588   return value;
589 }
590
591 } // namespace Internal
592 } // namespace Toolkit
593 } // namespace Dali