Merge "Add a callback to get textfitted font size." into devel/master
authorjoogab yun <joogab.yun@samsung.com>
Mon, 22 Nov 2021 06:40:15 +0000 (06:40 +0000)
committerGerrit Code Review <gerrit@review>
Mon, 22 Nov 2021 06:40:15 +0000 (06:40 +0000)
41 files changed:
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.cpp
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-utils.h
automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Text.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp
automated-tests/src/dali-toolkit/utc-Dali-ImageVisual.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp
dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.cpp [new file with mode: 0644]
dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h [new file with mode: 0644]
dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp
dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.h
dali-toolkit/internal/controls/text-controls/common-text-utils.cpp
dali-toolkit/internal/controls/text-controls/common-text-utils.h
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp
dali-toolkit/internal/file.list
dali-toolkit/internal/text/emoji-helper.cpp [new file with mode: 0644]
dali-toolkit/internal/text/emoji-helper.h [new file with mode: 0644]
dali-toolkit/internal/text/hidden-text.cpp
dali-toolkit/internal/text/hidden-text.h
dali-toolkit/internal/text/multi-language-support-impl.cpp
dali-toolkit/internal/text/multi-language-support-impl.h
dali-toolkit/internal/text/text-controller-impl-data-clearer.cpp [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-impl-data-clearer.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-impl.cpp
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/text/text-controller-input-properties.cpp [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-input-properties.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-relayouter.cpp
dali-toolkit/internal/text/text-controller-text-updater.cpp
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/text/text-controller.h
dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp
dali-toolkit/internal/visuals/animated-vector-image/animated-vector-image-visual.cpp
dali-toolkit/internal/visuals/image-visual-shader-factory.cpp
dali-toolkit/internal/visuals/image-visual-shader-factory.h
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/npatch/npatch-visual.cpp
dali-toolkit/internal/visuals/svg/svg-visual.cpp
dali-toolkit/internal/visuals/visual-factory-cache.h
dali-toolkit/public-api/dali-toolkit-version.cpp
packaging/dali-toolkit.spec

index 64a8473..701977d 100755 (executable)
@@ -460,6 +460,47 @@ void ConfigureTextEditor( ControllerPtr controller )
   controller->SetMatchLayoutDirection(DevelText::MatchLayoutDirection::CONTENTS);
 }
 
+
+Vector<FontDescriptionRun> CreateSingleFontDescription(
+                    const CharacterRun&         characterRun,
+                    const std::string           fontFamilyName,
+                    const FontWeight            weight,
+                    const FontWidth             width,
+                    const FontSlant             slant,
+                    const PointSize26Dot6       size,
+                    const bool                  familyDefined,
+                    const bool                  weightDefined,
+                    const bool                  widthDefined,
+                    const bool                  slantDefined,
+                    const bool                  sizeDefined)
+{
+
+  FontDescriptionRun fontDescriptionRun =
+  {
+    characterRun,
+    nullptr,
+    0u,
+    weight,
+    width,
+    slant,
+    size,
+    familyDefined,
+    weightDefined,
+    widthDefined,
+    slantDefined,
+    sizeDefined
+  };
+
+  fontDescriptionRun.familyLength = fontFamilyName.size();
+  fontDescriptionRun.familyName = new char[fontDescriptionRun.familyLength];
+  memcpy( fontDescriptionRun.familyName, fontFamilyName.c_str(), fontDescriptionRun.familyLength );
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  fontDescriptionRuns.PushBack(fontDescriptionRun);
+
+  return fontDescriptionRuns;
+}
+
 } // namespace Text
 
 } // namespace Toolkit
index ccfbe98..0b9f592 100644 (file)
@@ -93,6 +93,37 @@ void ConfigureTextField( ControllerPtr controller );
  */
 void ConfigureTextEditor( ControllerPtr controller );
 
+
+/**
+ * @brief Creates one FontDescriptionRun then add it to FontDescription list.
+ *
+ * @param[in] characterRun The initial character index and the number of characters of the run.
+ * @param[in] fontFamilyName The font's family name.
+ * @param[in] weight The font's weight.
+ * @param[in] width The font's width.
+ * @param[in] slant The font's slant.
+ * @param[in] size Whether the font's family is defined.
+ * @param[in] familyDefined Whether the font's weight is defined.
+ * @param[in] weightDefined Whether the font's width is defined.
+ * @param[in] widthDefined Whether the ellipsis layout option is enabled.
+ * @param[in] slantDefined Whether the font's slant is defined.
+ * @param[in] sizeDefined Whether the font's size is defined.
+
+* @return vector contains one FontDescriptionRun.
+ */
+Vector<FontDescriptionRun> CreateSingleFontDescription(
+                    const CharacterRun&         characterRun,
+                    const std::string           fontFamilyName,
+                    const FontWeight            weight,
+                    const FontWidth             width,
+                    const FontSlant             slant,
+                    const PointSize26Dot6       size,
+                    const bool                  familyDefined,
+                    const bool                  weightDefined,
+                    const bool                  widthDefined,
+                    const bool                  slantDefined,
+                    const bool                  sizeDefined);
+
 } // namespace Text
 
 } // namespace Toolkit
index 6b77f00..eea6fa6 100644 (file)
@@ -23,6 +23,7 @@
 #include <dali-toolkit/dali-toolkit.h>
 
 #include <dali/devel-api/adaptor-framework/accessibility.h>
+#include <dali-toolkit/internal/controls/text-controls/text-field-impl.h>
 #include <dali-toolkit/internal/controls/text-controls/text-editor-impl.h>
 
 #include <automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/dbus-wrapper.h>
@@ -221,6 +222,20 @@ int utcDaliAccessibilityTextFieldGetText(void)
     DALI_TEST_EQUALS( x->GetText( 0, 0 ), "", TEST_LOCATION );
     field.SetProperty( Toolkit::TextField::Property::TEXT, "exemplary_text" );
     DALI_TEST_EQUALS( x->GetText( 0, 9 ), "exemplary", TEST_LOCATION );
+
+    Dali::Property::Map hiddenInputSettings;
+    hiddenInputSettings[ Toolkit::HiddenInput::Property::MODE ] = Toolkit::HiddenInput::Mode::HIDE_ALL;
+
+    field.SetProperty( Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS, hiddenInputSettings );
+
+    DALI_TEST_EQUALS( x->GetName(), "", TEST_LOCATION );
+    DALI_TEST_EQUALS( x->GetText( 0, 9 ), "*********", TEST_LOCATION );
+
+    hiddenInputSettings[ Toolkit::HiddenInput::Property::SUBSTITUTE_CHARACTER ] = 0x23;
+    field.SetProperty( Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS, hiddenInputSettings );
+
+    DALI_TEST_EQUALS( x->GetName(), "", TEST_LOCATION );
+    DALI_TEST_EQUALS( x->GetText( 0, 9 ), "#########", TEST_LOCATION );
   }
 
   END_TEST;
@@ -294,6 +309,16 @@ int utcDaliAccessibilityTextFieldGetTextAtOffset(void)
     DALI_TEST_EQUALS( range.content, " test sentence", TEST_LOCATION );
     DALI_TEST_EQUALS( range.startOffset, 25, TEST_LOCATION );
     DALI_TEST_EQUALS( range.endOffset, 39, TEST_LOCATION );
+
+    Dali::Property::Map hiddenInputSettings;
+    hiddenInputSettings[ Toolkit::HiddenInput::Property::MODE ] = Toolkit::HiddenInput::Mode::HIDE_ALL;
+    hiddenInputSettings[ Toolkit::HiddenInput::Property::SUBSTITUTE_CHARACTER ] = 0x23;
+    field.SetProperty( Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS, hiddenInputSettings );
+    range = x->GetTextAtOffset( 8, Dali::Accessibility::TextBoundary::LINE );
+    DALI_TEST_EQUALS( range.content, "", TEST_LOCATION );
+    DALI_TEST_EQUALS( range.startOffset, 0, TEST_LOCATION );
+    DALI_TEST_EQUALS( range.endOffset, 0, TEST_LOCATION );
+
   }
 
   END_TEST;
@@ -321,6 +346,15 @@ int utcDaliAccessibilityTextFieldGetSetRangeOfSelection(void)
     DALI_TEST_EQUALS( range.startOffset, 4, TEST_LOCATION );
     DALI_TEST_EQUALS( range.endOffset, 9, TEST_LOCATION );
     DALI_TEST_EQUALS( range.content, "plary", TEST_LOCATION );
+
+    Dali::Property::Map hiddenInputSettings;
+    hiddenInputSettings[ Toolkit::HiddenInput::Property::MODE ] = Toolkit::HiddenInput::Mode::HIDE_ALL;
+    field.SetProperty( Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS, hiddenInputSettings );
+
+    range = x->GetRangeOfSelection( 0 );
+    DALI_TEST_EQUALS( range.startOffset, 4, TEST_LOCATION );
+    DALI_TEST_EQUALS( range.endOffset, 9, TEST_LOCATION );
+    DALI_TEST_EQUALS( range.content, "*****", TEST_LOCATION );
   }
 
   END_TEST;
index 8c056dd..272db31 100755 (executable)
@@ -119,6 +119,7 @@ struct ShapeInfoData
   Length*         charactersPerGlyph;                 ///< The characters per glyph.
   uint32_t        expectedNumberOfNewParagraphGlyphs; ///< The expected number of glyphs.
   GlyphIndex*     newParagraphGlyphs;                 ///< Indices to the new paragraphs glyphs.
+  bool            markupProcessorEnabled;             //< Enable markup processor to use markup text.
   Vector<FontDescriptionRun> fontDescriptions;        ///< Fonts which is used for text.
 };
 
@@ -140,7 +141,7 @@ bool ShapeInfoTest( const ShapeInfoData& data )
                    layoutSize,
                    textModel,
                    metrics,
-                   false,
+                   data.markupProcessorEnabled,
                    LineWrap::WORD,
                    false,
                    Toolkit::DevelText::EllipsisPosition::END );
@@ -337,6 +338,34 @@ void LoadSoftwareStylingFonts()
   fontClient.GetFontId( pathName + DEFAULT_FONT_DIR + "/roboto/Roboto-BoldItalic.ttf" );
 }
 
+void LoadEmojiFonts()
+{
+  TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+  fontClient.ClearCache();
+  fontClient.SetDpi( 96u, 96u );
+
+  char* pathNamePtr = get_current_dir_name();
+  const std::string pathName( pathNamePtr );
+  free( pathNamePtr );
+
+
+  TextAbstraction::FontDescription fontDescriptionText;
+  fontDescriptionText.path   = "";
+  fontDescriptionText.family = "DejaVuSans";
+  fontDescriptionText.width  = TextAbstraction::FontWidth::NONE;
+  fontDescriptionText.weight = TextAbstraction::FontWeight::NORMAL;
+  fontDescriptionText.slant  = TextAbstraction::FontSlant::NONE;
+  fontClient.GetFontId(fontDescriptionText, TextAbstraction::FontClient::DEFAULT_POINT_SIZE);
+
+  TextAbstraction::FontDescription fontDescriptionEmoji;
+  fontDescriptionEmoji.path   = "";
+  fontDescriptionEmoji.family = "NotoColorEmoji";
+  fontDescriptionEmoji.width  = TextAbstraction::FontWidth::NONE;
+  fontDescriptionEmoji.weight = TextAbstraction::FontWeight::NORMAL;
+  fontDescriptionEmoji.slant  = TextAbstraction::FontSlant::NONE;
+  fontClient.GetFontId(fontDescriptionEmoji, TextAbstraction::FontClient::DEFAULT_POINT_SIZE);
+}
+
 } // namespace
 
 //////////////////////////////////////////////////////////
@@ -671,7 +700,8 @@ int UtcDaliTextShape(void)
       nullptr,
       nullptr,
       0u,
-      nullptr
+      nullptr,
+      false,
     },
     {
       "Latin script",
@@ -684,6 +714,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph02,
       0u,
       nullptr,
+      false,
       fontDescriptions01
     },
     {
@@ -697,6 +728,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph03,
       2u,
       newParagraphGlyphs03,
+      false,
       fontDescriptions02
     },
     {
@@ -710,6 +742,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph04,
       0u,
       nullptr,
+      false,
       fontDescriptions03
     },
     {
@@ -723,7 +756,8 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs05,
-      fontDescriptions04
+      false,
+      fontDescriptions04,
     },
     {
       "Latin script with some paragraphs. Update mid paragraph.",
@@ -736,6 +770,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs06,
+      false,
       fontDescriptions05
     },
     {
@@ -749,6 +784,7 @@ int UtcDaliTextShape(void)
       charactersPerGlyph05,
       1u,
       newParagraphGlyphs07,
+      false,
       fontDescriptions06
     },
   };
@@ -927,6 +963,7 @@ int UtcDaliTextSoftwareStyling(void)
       charactersPerGlyph,
       0u,
       nullptr,
+      false,
       fontDescriptions01
     },
     {
@@ -940,6 +977,7 @@ int UtcDaliTextSoftwareStyling(void)
       charactersPerGlyph,
       0u,
       nullptr,
+      false,
       fontDescriptions02
     }
   };
@@ -960,3 +998,142 @@ int UtcDaliTextSoftwareStyling(void)
   tet_result(TET_PASS);
   END_TEST;
 }
+
+
+int UtcDaliTextShapeEmojiSequences(void)
+{
+
+  ToolkitTestApplication application;
+
+  tet_infoline(" UtcDaliTextShapeEmojiSequences");
+
+  const std::string colorFontFamily( "NotoColorEmoji" );
+  const std::string textFontFamily( "DejaVuSans" );
+
+  LoadEmojiFonts();
+
+  //Common attributes for font Descriptions
+  CharacterRun    characterRun = {0u, 2u};
+  FontWeight      weight       = TextAbstraction::FontWeight::NORMAL;
+  FontWidth       width        = TextAbstraction::FontWidth::NORMAL;
+  FontSlant       slant        = TextAbstraction::FontSlant::ITALIC;
+  PointSize26Dot6 size         = TextAbstraction::FontClient::DEFAULT_POINT_SIZE;
+
+  bool familyDefined = true;
+  bool weightDefined = false;
+  bool widthDefined  = false;
+  bool slantDefined  = false;
+  bool sizeDefined   = false;
+
+
+  // variation selector 16 (Emoji)
+  struct GlyphInfoData glyphsVS16[] =
+  {
+    { 2u, 74u, 0.f, 0.f, 0.f, 0.f, 39.0f, 0.f, false, false },
+  };
+  CharacterIndex characterIndicesVS16[] = { 0u, 1u};
+  Length charactersPerGlyphVS16[] = { 2u };
+
+
+
+  // variation selector 15 (Text)
+  struct GlyphInfoData glyphsVS15[] =
+  {
+    { 1u, 3842u, 0.f, 0.f, 0.f, 0.f, 14.0f, 0.f, false, false },
+    { 1u, 8203u, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, false, false },
+  };
+  CharacterIndex characterIndicesVS15[] = { 0u, 0u};
+  Length charactersPerGlyphVS15[] = { 0u, 2u };
+
+  //Font Descriptions
+  Vector<FontDescriptionRun> fontDescriptionsColorVS16 =
+                    CreateSingleFontDescription (characterRun, colorFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsColorVS15 =
+                    CreateSingleFontDescription (characterRun, colorFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsTextVS16 =
+                    CreateSingleFontDescription (characterRun, textFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+  Vector<FontDescriptionRun> fontDescriptionsTextVS15 =
+                    CreateSingleFontDescription (characterRun, textFontFamily, weight, width,
+                     slant, size, familyDefined, weightDefined, widthDefined, slantDefined, sizeDefined);
+
+
+  struct ShapeInfoData data[] =
+  {
+     {
+      "EMOJI Sequence: Color Font with VS16",
+      "&#x262a;&#xfe0f;",
+      0u,
+      2u,
+      1u,
+      glyphsVS16,
+      characterIndicesVS16,
+      charactersPerGlyphVS16,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsColorVS16
+    },
+    {
+      "EMOJI Sequence: Color Font with VS15",
+      "&#x262a;&#xfe0e;",
+      0u,
+      2u,
+      2u,
+      glyphsVS15,
+      characterIndicesVS15,
+      charactersPerGlyphVS15,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsColorVS15
+    },
+    {
+      "EMOJI Sequence: Text Font with VS16",
+      "&#x262a;&#xfe0f;",
+      0u,
+      2u,
+      1u,
+      glyphsVS16,
+      characterIndicesVS16,
+      charactersPerGlyphVS16,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsTextVS16
+    },
+    {
+      "EMOJI Sequence: Text Font with VS15",
+      "&#x262a;&#xfe0e;",
+      0u,
+      2u,
+      2u,
+      glyphsVS15,
+      characterIndicesVS15,
+      charactersPerGlyphVS15,
+      0u,
+      nullptr,
+      true,
+      fontDescriptionsTextVS15
+    },
+  };
+
+  const unsigned int numberOfTests = 4u;
+
+  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  {
+     tet_infoline( data[index].description.c_str());
+    if( !ShapeInfoTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
index cd834df..51b9923 100644 (file)
@@ -422,7 +422,6 @@ int UtcDaliImageVisualRemoteImageLoad(void)
   END_TEST;
 }
 
-
 int UtcDaliImageVisualWithNativeImage(void)
 {
   ToolkitTestApplication application;
@@ -468,6 +467,71 @@ int UtcDaliImageVisualWithNativeImage(void)
   END_TEST;
 }
 
+int UtcDaliImageVisualWithNativeImageCustomShader(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline( "Use Native Image as url and Use custom shader" );
+
+  NativeImageSourcePtr nativeImageSource = NativeImageSource::New(500, 500, NativeImageSource::COLOR_DEPTH_DEFAULT);
+  ImageUrl imageUrl = Dali::Toolkit::Image::GenerateUrl(nativeImageSource);
+  std::string url = imageUrl.GetUrl();
+
+  VisualFactory factory = VisualFactory::Get();
+  DALI_TEST_CHECK( factory );
+
+  Property::Map propertyMap;
+  Property::Map shaderMap;
+  const std::string customVertexShaderSource = "Foobar";
+  const std::string customFragmentShaderSource = "Foobar";
+  shaderMap[Toolkit::Visual::Shader::Property::FRAGMENT_SHADER] = customFragmentShaderSource;
+  shaderMap[Toolkit::Visual::Shader::Property::VERTEX_SHADER] = customVertexShaderSource;
+
+  propertyMap.Insert( Toolkit::Visual::Property::TYPE,   Visual::IMAGE );
+  propertyMap.Insert( Toolkit::Visual::Property::SHADER, shaderMap );
+  propertyMap.Insert( ImageVisual::Property::URL,        url );
+
+  Visual::Base visual = factory.CreateVisual( propertyMap );
+  DALI_TEST_CHECK( visual );
+
+  DummyControl actor = DummyControl::New();
+  DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+  dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual );
+
+  actor.SetProperty( Actor::Property::SIZE, Vector2( 200.f, 200.f ) );
+  actor.SetProperty( Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER );
+
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 0u, TEST_LOCATION );
+
+  application.GetScene().Add( actor );
+
+  DALI_TEST_EQUALS( actor.GetRendererCount(), 1u, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render(16);
+
+  Renderer renderer = actor.GetRendererAt(0);
+  Shader shader = renderer.GetShader();
+
+  Property::Value value = shader.GetProperty(Shader::Property::PROGRAM);
+  DALI_TEST_CHECK(value.GetType() == Property::MAP);
+  const Property::Map* outMap = value.GetMap();
+  std::string fragmentShaderSource = (*outMap)["fragment"].Get<std::string>();
+  std::string vertexShaderSource = (*outMap)["vertex"].Get<std::string>();
+
+  // Compare vertex shader is equal
+  DALI_TEST_EQUALS( customVertexShaderSource, vertexShaderSource, TEST_LOCATION );
+
+  // Check fragment shader changed
+  const char* fragmentPrefix = Dali::NativeImageSourceTest::GetCustomFragmentPrefix();
+  size_t pos = fragmentShaderSource.find(fragmentPrefix);
+
+  DALI_TEST_EQUALS( pos != std::string::npos, true, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( std::string(fragmentPrefix) + customFragmentShaderSource, fragmentShaderSource, TEST_LOCATION );
+
+  END_TEST;
+}
+
 int UtcDaliImageVisualWithNativeImageRemoved(void)
 {
   ToolkitTestApplication application;
index aa7f5bd..b0f9764 100644 (file)
@@ -851,6 +851,37 @@ int UtcDaliToolkitTextLabelEmojisP(void)
   application.SendNotification();
   application.Render();
 
+  // EMOJI Sequences case for coverage.
+  std::string emojiSequences =
+       "Text VS15 &#x262a;&#xfe0e;\n"                                                         //text presentation sequence and selector
+      "Color VS16 &#x262a;&#xfe0f;\n"                                                        //emoji presentation sequence and selector
+      "Default &#x262a; \n"                                                                  //default presentation
+      "FamilyManWomanGirlBoy &#x1F468;&#x200D;&#x1F469;&#x200D;&#x1F467;&#x200D;&#x1F466;\n" // emoji multi zwj sequence
+      "WomanScientist &#x1f469;&#x200d;&#x1f52c;\n"                                          // emoji zwj sequence
+      "WomanScientistLightSkinTone&#x1F469;&#x1F3FB;&#x200D;&#x1F52C; \n"                    //emoji modifier sequence: skin tone & JWZ
+      "LeftRightArrowText&#x2194;&#xfe0e;\n"                                                 //text presentation sequence and selector
+      "LeftRightArrowEmoji&#x2194;&#xfe0f;\n"                                                //emoji presentation sequence and selector
+      "SouthKoreaFlag&#x1f1f0;&#x1f1f7;\n"                                                   //emoji flag sequence
+      "JordanFlag&#x1f1ef;&#x1f1f4;\n"                                                       // emoji flag sequence
+      "EnglandFlag&#x1F3F4;&#xE0067;&#xE0062;&#xE0065;&#xE006E;&#xE0067;&#xE007F;\n"         //emoji tag sequence like England flag
+      "Runner &#x1f3c3;&#x200d;&#x27a1;&#xfe0f; \n"
+      "VictoryHandMediumLightSkinTone:&#x270C;&#xFE0F;&#x1F3FC;\n"               //emoji modifier sequence: skin tone
+      "RainbowFlag:&#x1F3F3;&#xFE0F;&#x200D;&#x1F308; \n"                        //emoji zwj sequence: Rainbow Flag
+      "keycap# &#x0023;&#xFE0F;&#x20E3; \n"                                      // fully-qualified  emoji keycap sequence
+      "keycap#_text &#x0023;&#x20E3; \n"                                         // unqualified emoji keycap sequence
+      "keycap3 &#x0033;&#xfe0f;&#x20e3; \n"                                      // fully-qualified  emoji keycap sequence
+      "keycap3_text &#x0033;&#x20e3; \n"                                         // unqualified emoji keycap sequence
+      "two adjacent glyphs &#x262a;&#xfe0f;&#xfe0f;&#xfe0f;&#x262a;&#xfe0f;\n"   //This line should be rendered as two adjacent glyphs
+      "Digit 8&#xfe0f; 8&#xfe0e; 8\n"                                            // should be rendered according to selector
+      "Surfing Medium Skin Female:  &#x1f3c4;&#x1f3fc;&#x200d;&#x2640;&#xfe0f;"; // Person Surfing + Medium Skin Tone +? Zero Width Joiner + Female Sign
+
+  label.SetProperty( TextLabel::Property::TEXT, emojiSequences );
+  label.SetProperty( TextLabel::Property::ENABLE_MARKUP, true );
+  label.SetProperty( TextLabel::Property::MULTI_LINE, true);
+  label.SetProperty( TextLabel::Property::ELLIPSIS, false);
+
+  application.SendNotification();
+  application.Render();
   END_TEST;
 }
 
diff --git a/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.cpp b/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.cpp
new file mode 100644 (file)
index 0000000..9c1272c
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.h>
+#include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-mode.h>
+
+namespace Dali::Toolkit::Internal
+{
+
+void ScrollViewPropertyHandler::Set(BaseObject* object, Property::Index index, const Property::Value& value)
+{
+  Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
+
+  if(scrollView)
+  {
+    ScrollView& scrollViewImpl(GetImpl(scrollView));
+    switch(index)
+    {
+      case Toolkit::ScrollView::Property::WRAP_ENABLED:
+      {
+        scrollViewImpl.SetWrapMode(value.Get<bool>());
+        break;
+      }
+      case Toolkit::ScrollView::Property::PANNING_ENABLED:
+      {
+        scrollViewImpl.SetScrollSensitive(value.Get<bool>());
+        break;
+      }
+      case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
+      {
+        scrollViewImpl.SetAxisAutoLock(value.Get<bool>());
+        break;
+      }
+      case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
+      {
+        scrollViewImpl.SetWheelScrollDistanceStep(value.Get<Vector2>());
+        break;
+      }
+      case Toolkit::ScrollView::Property::SCROLL_MODE:
+      {
+        const Property::Map* map = value.GetMap();
+        if(map)
+        {
+          SetScrollMode(scrollViewImpl, *map);
+        }
+      }
+    }
+  }
+}
+
+Property::Value ScrollViewPropertyHandler::Get(BaseObject* object, Property::Index index)
+{
+  Property::Value value;
+
+  Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
+
+  if(scrollView)
+  {
+    ScrollView& scrollViewImpl(GetImpl(scrollView));
+    switch(index)
+    {
+      case Toolkit::ScrollView::Property::WRAP_ENABLED:
+      {
+        value = scrollViewImpl.GetWrapMode();
+        break;
+      }
+      case Toolkit::ScrollView::Property::PANNING_ENABLED:
+      {
+        value = scrollViewImpl.GetScrollSensitive();
+        break;
+      }
+      case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
+      {
+        value = scrollViewImpl.GetAxisAutoLock();
+        break;
+      }
+      case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
+      {
+        value = scrollViewImpl.GetWheelScrollDistanceStep();
+        break;
+      }
+    }
+  }
+
+  return value;
+}
+
+void ScrollViewPropertyHandler::SetScrollMode(ScrollView& scrollView, const Property::Map& scrollModeMap)
+{
+  Toolkit::RulerPtr rulerX, rulerY;
+
+  // Check the scroll mode in the X axis
+  bool             xAxisScrollEnabled = true;
+  Property::Value* valuePtr           = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_ENABLED, "xAxisScrollEnabled");
+  if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
+  {
+    valuePtr->Get(xAxisScrollEnabled);
+  }
+
+  if(!xAxisScrollEnabled)
+  {
+    // Default ruler and disabled
+    rulerX = new Toolkit::DefaultRuler();
+    rulerX->Disable();
+  }
+  else
+  {
+    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SNAP_TO_INTERVAL, "xAxisSnapToInterval");
+    float xAxisSnapToInterval = 0.0f;
+    if(valuePtr && valuePtr->Get(xAxisSnapToInterval))
+    {
+      // Fixed ruler and enabled
+      rulerX = new Toolkit::FixedRuler(xAxisSnapToInterval);
+    }
+    else
+    {
+      // Default ruler and enabled
+      rulerX = new Toolkit::DefaultRuler();
+    }
+
+    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_BOUNDARY, "xAxisScrollBoundary");
+    float xAxisScrollBoundary = 0.0f;
+    if(valuePtr && valuePtr->Get(xAxisScrollBoundary))
+    {
+      // By default ruler domain is disabled unless set
+      rulerX->SetDomain(Toolkit::RulerDomain(0, xAxisScrollBoundary, true));
+    }
+  }
+
+  // Check the scroll mode in the Y axis
+  bool yAxisScrollEnabled = true;
+  valuePtr                = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_ENABLED, "yAxisScrollEnabled");
+  if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
+  {
+    valuePtr->Get(yAxisScrollEnabled);
+  }
+
+  if(!yAxisScrollEnabled)
+  {
+    // Default ruler and disabled
+    rulerY = new Toolkit::DefaultRuler();
+    rulerY->Disable();
+  }
+  else
+  {
+    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SNAP_TO_INTERVAL, "yAxisSnapToInterval");
+    float yAxisSnapToInterval = 0.0f;
+    if(valuePtr && valuePtr->Get(yAxisSnapToInterval))
+    {
+      // Fixed ruler and enabled
+      rulerY = new Toolkit::FixedRuler(yAxisSnapToInterval);
+    }
+    else
+    {
+      // Default ruler and enabled
+      rulerY = new Toolkit::DefaultRuler();
+    }
+
+    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_BOUNDARY, "yAxisScrollBoundary");
+    float yAxisScrollBoundary = 0.0f;
+    if(valuePtr && valuePtr->Get(yAxisScrollBoundary))
+    {
+      // By default ruler domain is disabled unless set
+      rulerY->SetDomain(Toolkit::RulerDomain(0, yAxisScrollBoundary, true));
+    }
+  }
+
+  scrollView.SetRulerX(rulerX);
+  scrollView.SetRulerY(rulerY);
+}
+
+void ScrollViewPropertyHandler::UpdatePropertyDomain(ScrollView& scrollView)
+{
+  Actor   self                  = scrollView.Self();
+  Vector3 size                  = self.GetTargetSize();
+  Vector2 min                   = scrollView.mMinScroll;
+  Vector2 max                   = scrollView.mMaxScroll;
+  bool    scrollPositionChanged = false;
+  bool    domainChanged         = false;
+
+  bool canScrollVertical   = false;
+  bool canScrollHorizontal = false;
+  scrollView.UpdateLocalScrollProperties();
+  if(scrollView.mRulerX->IsEnabled())
+  {
+    const Toolkit::RulerDomain& rulerDomain = scrollView.mRulerX->GetDomain();
+    if(fabsf(min.x - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.x - rulerDomain.max) > Math::MACHINE_EPSILON_100)
+    {
+      domainChanged = true;
+      min.x         = rulerDomain.min;
+      max.x         = rulerDomain.max;
+
+      // make sure new scroll value is within new domain
+      if(scrollView.mScrollPrePosition.x < min.x || scrollView.mScrollPrePosition.x > max.x)
+      {
+        scrollPositionChanged = true;
+        scrollView.mScrollPrePosition.x  = Clamp(scrollView.mScrollPrePosition.x, -(max.x - size.x), -min.x);
+      }
+    }
+    if((fabsf(rulerDomain.max - rulerDomain.min) - size.x) > Math::MACHINE_EPSILON_100)
+    {
+      canScrollHorizontal = true;
+    }
+  }
+  else if(fabs(min.x) > Math::MACHINE_EPSILON_100 || fabs(max.x) > Math::MACHINE_EPSILON_100)
+  {
+    // need to reset to 0
+    domainChanged       = true;
+    min.x               = 0.0f;
+    max.x               = 0.0f;
+    canScrollHorizontal = false;
+  }
+
+  if(scrollView.mRulerY->IsEnabled())
+  {
+    const Toolkit::RulerDomain& rulerDomain = scrollView.mRulerY->GetDomain();
+    if(fabsf(min.y - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.y - rulerDomain.max) > Math::MACHINE_EPSILON_100)
+    {
+      domainChanged = true;
+      min.y         = rulerDomain.min;
+      max.y         = rulerDomain.max;
+
+      // make sure new scroll value is within new domain
+      if(scrollView.mScrollPrePosition.y < min.y || scrollView.mScrollPrePosition.y > max.y)
+      {
+        scrollPositionChanged = true;
+        scrollView.mScrollPrePosition.y  = Clamp(scrollView.mScrollPrePosition.y, -(max.y - size.y), -min.y);
+      }
+    }
+    if((fabsf(rulerDomain.max - rulerDomain.min) - size.y) > Math::MACHINE_EPSILON_100)
+    {
+      canScrollVertical = true;
+    }
+  }
+  else if(fabs(min.y) > Math::MACHINE_EPSILON_100 || fabs(max.y) > Math::MACHINE_EPSILON_100)
+  {
+    // need to reset to 0
+    domainChanged     = true;
+    min.y             = 0.0f;
+    max.y             = 0.0f;
+    canScrollVertical = false;
+  }
+
+  // avoid setting properties if possible, otherwise this will cause an entire update as well as triggering constraints using each property we update
+  if(scrollView.mCanScrollVertical != canScrollVertical)
+  {
+    scrollView.mCanScrollVertical = canScrollVertical;
+    self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, canScrollVertical);
+  }
+  if(scrollView.mCanScrollHorizontal != canScrollHorizontal)
+  {
+    scrollView.mCanScrollHorizontal = canScrollHorizontal;
+    self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, canScrollHorizontal);
+  }
+  if(scrollPositionChanged)
+  {
+    self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, scrollView.mScrollPrePosition);
+  }
+  if(domainChanged)
+  {
+    scrollView.mMinScroll = min;
+    scrollView.mMaxScroll = max;
+    self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN, scrollView.mMinScroll);
+    self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, scrollView.mMaxScroll);
+  }
+}
+
+} // namespace Dali::Toolkit::Internal
diff --git a/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h b/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h
new file mode 100644 (file)
index 0000000..1923cfc
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef DALI_TOOLKIT_INTERNAL_SCROLL_VIEW_IMPL_PROPERTY_HANDLER_H
+#define DALI_TOOLKIT_INTERNAL_SCROLL_VIEW_IMPL_PROPERTY_HANDLER_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/object/base-object.h>
+#include <dali/public-api/object/property.h>
+
+namespace Dali::Toolkit::Internal
+{
+class ScrollView;
+
+/// Handles the properties in scroll view calling the appropriate scroll view methods
+struct ScrollViewPropertyHandler
+{
+  /**
+   * @brief Sets the property on the given scroll-view object.
+   *
+   * @param object The scrollview object
+   * @param index The index to set
+   * @param value The value to set
+   */
+  static void Set(BaseObject* object, Property::Index index, const Property::Value& value);
+
+  /**
+   * @brief Retrieves the value of a scroll-view property.
+   *
+   * @param object The scrollview object
+   * @param index The index whose value is to be retrieved
+   * @return
+   */
+  static Property::Value Get(BaseObject* object, Property::Index index);
+
+  /**
+   * Set up default rulers using a property map
+   * @param[in] scrollView    The scroll view to apply this on
+   * @param[in] scrollModeMap A map defining the characteristics of X and Y scrolling
+   * using either FixedRuler or DefaultRuler.
+   */
+  static void SetScrollMode(ScrollView& scrollView, const Property::Map& scrollModeMap);
+
+  /**
+   * This is called whenever the Scroll Rulers are modified.
+   *
+   * This will update the properties: 'scrollPositionMin' * and 'scrollPositionMax' to reflect the changes.
+   * @param scrollView The Scroll View to modify
+   */
+  static void UpdatePropertyDomain(ScrollView& scrollView);
+};
+
+} // namespace Dali::Toolkit::Internal
+
+#endif // DALI_TOOLKIT_INTERNAL_SCROLL_VIEW_IMPL_PROPERTY_HANDLER_H
index 86cfc56..712cb17 100644 (file)
@@ -36,6 +36,7 @@
 #include <dali-toolkit/devel-api/controls/scroll-bar/scroll-bar.h>
 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-overshoot-indicator-impl.h>
 #include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-effect-impl.h>
+#include <dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl-property-handler.h>
 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-mode.h>
 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view-constraints.h>
 #include <dali-toolkit/public-api/controls/scrollable/scroll-view/scroll-view.h>
@@ -673,7 +674,7 @@ void ScrollView::OnInitialize()
   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, mCanScrollVertical);
   self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, mCanScrollHorizontal);
 
-  UpdatePropertyDomain();
+  ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
   mConstraints.SetInternalConstraints(*this);
 
   // Connect wheel event
@@ -794,7 +795,7 @@ void ScrollView::SetRulerX(RulerPtr ruler)
 {
   mRulerX = ruler;
 
-  UpdatePropertyDomain();
+  ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
   mConstraints.UpdateMainInternalConstraint(*this);
 }
 
@@ -802,107 +803,10 @@ void ScrollView::SetRulerY(RulerPtr ruler)
 {
   mRulerY = ruler;
 
-  UpdatePropertyDomain();
+  ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
   mConstraints.UpdateMainInternalConstraint(*this);
 }
 
-void ScrollView::UpdatePropertyDomain()
-{
-  Actor   self                  = Self();
-  Vector3 size                  = self.GetTargetSize();
-  Vector2 min                   = mMinScroll;
-  Vector2 max                   = mMaxScroll;
-  bool    scrollPositionChanged = false;
-  bool    domainChanged         = false;
-
-  bool canScrollVertical   = false;
-  bool canScrollHorizontal = false;
-  UpdateLocalScrollProperties();
-  if(mRulerX->IsEnabled())
-  {
-    const Toolkit::RulerDomain& rulerDomain = mRulerX->GetDomain();
-    if(fabsf(min.x - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.x - rulerDomain.max) > Math::MACHINE_EPSILON_100)
-    {
-      domainChanged = true;
-      min.x         = rulerDomain.min;
-      max.x         = rulerDomain.max;
-
-      // make sure new scroll value is within new domain
-      if(mScrollPrePosition.x < min.x || mScrollPrePosition.x > max.x)
-      {
-        scrollPositionChanged = true;
-        mScrollPrePosition.x  = Clamp(mScrollPrePosition.x, -(max.x - size.x), -min.x);
-      }
-    }
-    if((fabsf(rulerDomain.max - rulerDomain.min) - size.x) > Math::MACHINE_EPSILON_100)
-    {
-      canScrollHorizontal = true;
-    }
-  }
-  else if(fabs(min.x) > Math::MACHINE_EPSILON_100 || fabs(max.x) > Math::MACHINE_EPSILON_100)
-  {
-    // need to reset to 0
-    domainChanged       = true;
-    min.x               = 0.0f;
-    max.x               = 0.0f;
-    canScrollHorizontal = false;
-  }
-
-  if(mRulerY->IsEnabled())
-  {
-    const Toolkit::RulerDomain& rulerDomain = mRulerY->GetDomain();
-    if(fabsf(min.y - rulerDomain.min) > Math::MACHINE_EPSILON_100 || fabsf(max.y - rulerDomain.max) > Math::MACHINE_EPSILON_100)
-    {
-      domainChanged = true;
-      min.y         = rulerDomain.min;
-      max.y         = rulerDomain.max;
-
-      // make sure new scroll value is within new domain
-      if(mScrollPrePosition.y < min.y || mScrollPrePosition.y > max.y)
-      {
-        scrollPositionChanged = true;
-        mScrollPrePosition.y  = Clamp(mScrollPrePosition.y, -(max.y - size.y), -min.y);
-      }
-    }
-    if((fabsf(rulerDomain.max - rulerDomain.min) - size.y) > Math::MACHINE_EPSILON_100)
-    {
-      canScrollVertical = true;
-    }
-  }
-  else if(fabs(min.y) > Math::MACHINE_EPSILON_100 || fabs(max.y) > Math::MACHINE_EPSILON_100)
-  {
-    // need to reset to 0
-    domainChanged     = true;
-    min.y             = 0.0f;
-    max.y             = 0.0f;
-    canScrollVertical = false;
-  }
-
-  // avoid setting properties if possible, otherwise this will cause an entire update as well as triggering constraints using each property we update
-  if(mCanScrollVertical != canScrollVertical)
-  {
-    mCanScrollVertical = canScrollVertical;
-    self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, canScrollVertical);
-  }
-  if(mCanScrollHorizontal != canScrollHorizontal)
-  {
-    mCanScrollHorizontal = canScrollHorizontal;
-    self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, canScrollHorizontal);
-  }
-  if(scrollPositionChanged)
-  {
-    DALI_LOG_SCROLL_STATE("[0x%X] Domain Changed, setting SCROLL_PRE_POSITION To[%.2f, %.2f]", this, mScrollPrePosition.x, mScrollPrePosition.y);
-    self.SetProperty(Toolkit::ScrollView::Property::SCROLL_PRE_POSITION, mScrollPrePosition);
-  }
-  if(domainChanged)
-  {
-    mMinScroll = min;
-    mMaxScroll = max;
-    self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN, mMinScroll);
-    self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, mMaxScroll);
-  }
-}
-
 void ScrollView::SetScrollSensitive(bool sensitive)
 {
   Actor              self = Self();
@@ -1482,7 +1386,7 @@ bool ScrollView::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface*
 void ScrollView::OnSizeAnimation(Animation& animation, const Vector3& targetSize)
 {
   // need to update domain properties for new size
-  UpdatePropertyDomain();
+  ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
 }
 
 void ScrollView::OnSizeSet(const Vector3& size)
@@ -1497,7 +1401,7 @@ void ScrollView::OnSizeSet(const Vector3& size)
       mMaxOvershoot = mUserMaxOvershoot;
     }
   }
-  UpdatePropertyDomain();
+  ScrollViewPropertyHandler::UpdatePropertyDomain(*this);
   mConstraints.UpdateMainInternalConstraint(*this);
   if(IsOvershootEnabled())
   {
@@ -2180,45 +2084,6 @@ void ScrollView::FinishTransform()
   }
 }
 
-Vector2 ScrollView::GetOvershoot(Vector2& position) const
-{
-  Vector3 size = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
-  Vector2 overshoot;
-
-  const RulerDomain rulerDomainX = mRulerX->GetDomain();
-  const RulerDomain rulerDomainY = mRulerY->GetDomain();
-
-  if(mRulerX->IsEnabled() && rulerDomainX.enabled)
-  {
-    const float left  = rulerDomainX.min - position.x;
-    const float right = size.width - rulerDomainX.max - position.x;
-    if(left < 0)
-    {
-      overshoot.x = left;
-    }
-    else if(right > 0)
-    {
-      overshoot.x = right;
-    }
-  }
-
-  if(mRulerY->IsEnabled() && rulerDomainY.enabled)
-  {
-    const float top    = rulerDomainY.min - position.y;
-    const float bottom = size.height - rulerDomainY.max - position.y;
-    if(top < 0)
-    {
-      overshoot.y = top;
-    }
-    else if(bottom > 0)
-    {
-      overshoot.y = bottom;
-    }
-  }
-
-  return overshoot;
-}
-
 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
 {
   // Keep track of whether this is an AccessibilityPan
@@ -2263,164 +2128,12 @@ void ScrollView::WrapPosition(Vector2& position) const
 
 void ScrollView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
 {
-  Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
-
-  if(scrollView)
-  {
-    ScrollView& scrollViewImpl(GetImpl(scrollView));
-    switch(index)
-    {
-      case Toolkit::ScrollView::Property::WRAP_ENABLED:
-      {
-        scrollViewImpl.SetWrapMode(value.Get<bool>());
-        break;
-      }
-      case Toolkit::ScrollView::Property::PANNING_ENABLED:
-      {
-        scrollViewImpl.SetScrollSensitive(value.Get<bool>());
-        break;
-      }
-      case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
-      {
-        scrollViewImpl.SetAxisAutoLock(value.Get<bool>());
-        break;
-      }
-      case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
-      {
-        scrollViewImpl.SetWheelScrollDistanceStep(value.Get<Vector2>());
-        break;
-      }
-      case Toolkit::ScrollView::Property::SCROLL_MODE:
-      {
-        const Property::Map* map = value.GetMap();
-        if(map)
-        {
-          scrollViewImpl.SetScrollMode(*map);
-        }
-      }
-    }
-  }
+  ScrollViewPropertyHandler::Set(object, index, value);
 }
 
 Property::Value ScrollView::GetProperty(BaseObject* object, Property::Index index)
 {
-  Property::Value value;
-
-  Toolkit::ScrollView scrollView = Toolkit::ScrollView::DownCast(Dali::BaseHandle(object));
-
-  if(scrollView)
-  {
-    ScrollView& scrollViewImpl(GetImpl(scrollView));
-    switch(index)
-    {
-      case Toolkit::ScrollView::Property::WRAP_ENABLED:
-      {
-        value = scrollViewImpl.GetWrapMode();
-        break;
-      }
-      case Toolkit::ScrollView::Property::PANNING_ENABLED:
-      {
-        value = scrollViewImpl.GetScrollSensitive();
-        break;
-      }
-      case Toolkit::ScrollView::Property::AXIS_AUTO_LOCK_ENABLED:
-      {
-        value = scrollViewImpl.GetAxisAutoLock();
-        break;
-      }
-      case Toolkit::ScrollView::Property::WHEEL_SCROLL_DISTANCE_STEP:
-      {
-        value = scrollViewImpl.GetWheelScrollDistanceStep();
-        break;
-      }
-    }
-  }
-
-  return value;
-}
-
-void ScrollView::SetScrollMode(const Property::Map& scrollModeMap)
-{
-  Toolkit::RulerPtr rulerX, rulerY;
-
-  // Check the scroll mode in the X axis
-  bool             xAxisScrollEnabled = true;
-  Property::Value* valuePtr           = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_ENABLED, "xAxisScrollEnabled");
-  if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
-  {
-    valuePtr->Get(xAxisScrollEnabled);
-  }
-
-  if(!xAxisScrollEnabled)
-  {
-    // Default ruler and disabled
-    rulerX = new Toolkit::DefaultRuler();
-    rulerX->Disable();
-  }
-  else
-  {
-    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SNAP_TO_INTERVAL, "xAxisSnapToInterval");
-    float xAxisSnapToInterval = 0.0f;
-    if(valuePtr && valuePtr->Get(xAxisSnapToInterval))
-    {
-      // Fixed ruler and enabled
-      rulerX = new Toolkit::FixedRuler(xAxisSnapToInterval);
-    }
-    else
-    {
-      // Default ruler and enabled
-      rulerX = new Toolkit::DefaultRuler();
-    }
-
-    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::X_AXIS_SCROLL_BOUNDARY, "xAxisScrollBoundary");
-    float xAxisScrollBoundary = 0.0f;
-    if(valuePtr && valuePtr->Get(xAxisScrollBoundary))
-    {
-      // By default ruler domain is disabled unless set
-      rulerX->SetDomain(Toolkit::RulerDomain(0, xAxisScrollBoundary, true));
-    }
-  }
-
-  // Check the scroll mode in the Y axis
-  bool yAxisScrollEnabled = true;
-  valuePtr                = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_ENABLED, "yAxisScrollEnabled");
-  if(valuePtr && valuePtr->GetType() == Property::BOOLEAN)
-  {
-    valuePtr->Get(yAxisScrollEnabled);
-  }
-
-  if(!yAxisScrollEnabled)
-  {
-    // Default ruler and disabled
-    rulerY = new Toolkit::DefaultRuler();
-    rulerY->Disable();
-  }
-  else
-  {
-    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SNAP_TO_INTERVAL, "yAxisSnapToInterval");
-    float yAxisSnapToInterval = 0.0f;
-    if(valuePtr && valuePtr->Get(yAxisSnapToInterval))
-    {
-      // Fixed ruler and enabled
-      rulerY = new Toolkit::FixedRuler(yAxisSnapToInterval);
-    }
-    else
-    {
-      // Default ruler and enabled
-      rulerY = new Toolkit::DefaultRuler();
-    }
-
-    valuePtr                  = scrollModeMap.Find(Toolkit::ScrollMode::Y_AXIS_SCROLL_BOUNDARY, "yAxisScrollBoundary");
-    float yAxisScrollBoundary = 0.0f;
-    if(valuePtr && valuePtr->Get(yAxisScrollBoundary))
-    {
-      // By default ruler domain is disabled unless set
-      rulerY->SetDomain(Toolkit::RulerDomain(0, yAxisScrollBoundary, true));
-    }
-  }
-
-  SetRulerX(rulerX);
-  SetRulerY(rulerY);
+  return ScrollViewPropertyHandler::Get(object, index);
 }
 
 ScrollView::LockAxis GetLockAxis(const Vector2& panDelta, ScrollView::LockAxis currentLockAxis, float lockGradient)
index 4d871a7..4061727 100644 (file)
@@ -47,6 +47,8 @@ typedef IntrusivePtr<ScrollInternalConstraints> ScrollInternalConstraintsPtr;
 class ScrollOvershootIndicator;
 typedef IntrusivePtr<ScrollOvershootIndicator> ScrollOvershootIndicatorPtr;
 
+class ScrollViewPropertyHandler;
+
 /**
  * @copydoc Toolkit::ScrollView
  */
@@ -774,13 +776,6 @@ private:
   void SnapInternalYTo(float position);
 
   /**
-   * This is called internally whenever the Scroll Rulers are
-   * modified. This will update the properties: 'scrollPositionMin'
-   * and 'scrollPositionMax' to reflect the changes.
-   */
-  void UpdatePropertyDomain();
-
-  /**
    * Called when the gesture starts.
    */
   void GestureStarted();
@@ -821,20 +816,6 @@ private:
   void FinishTransform();
 
   /**
-   * Returns overshoot vector based on current position
-   *
-   * Overshoot vector is defined as how far outside of bounds
-   * the viewport is trying to view (prior to being clamped).
-   *
-   * an overshoot of (100,50), means user is in bottom right corner,
-   * trying to pan +100 to the right, and +50 below. This can be used
-   * to determine an effect, such as stretching.
-   *
-   * @param[in] position The position for which you wish to obtain overshoot vector
-   */
-  Vector2 GetOvershoot(Vector2& position) const;
-
-  /**
    * Clamps position within the domain set up by X/Y Rulers
    *
    * @param[in,out] position The position you wish to clamp
@@ -924,13 +905,6 @@ private:
    */
   void OnScrollUpdateNotification(Dali::PropertyNotification& source);
 
-  /**
-   * Set up default rulers using a property map
-   * @param[in] scrollModeMap A map defining the characteristics of X and Y scrolling
-   * using either FixedRuler or DefaultRuler.
-   */
-  void SetScrollMode(const Property::Map& scrollModeMap);
-
 private:
   // Undefined
   ScrollView(const ScrollView&);
@@ -1020,6 +994,7 @@ private:
   bool mTransientScrollBar : 1;         ///< True if scroll-bar should be automatically show/hidden during/after panning
 
   friend ScrollViewConstraints;
+  friend ScrollViewPropertyHandler;
 };
 
 /**
index 7331f3e..7033e6d 100644 (file)
@@ -27,7 +27,7 @@ void CommonTextUtils::RenderText(
   Text::RendererPtr                renderer,
   Text::ControllerPtr              controller,
   Text::DecoratorPtr               decorator,
-  float                            alignmentOffset,
+  float&                           alignmentOffset,
   Actor&                           renderableActor,
   Actor&                           backgroundActor,
   Toolkit::Control&                stencil,
index f2eaba7..f9069a2 100644 (file)
@@ -37,7 +37,7 @@ public:
    * @param[in] renderer pointer to the text renderer
    * @param[in] controller pointer to the text controller
    * @param[in] decorator pointer to the text decorator
-   * @param[in] alignmentOffset Alignment offset
+   * @param[in,out] alignmentOffset Alignment offset
    * @param[in,out] renderableActor Actor for rendering text
    * @param[in,out] backgroundActor Actor for rendering background
    * @param[in,out] stencil Clipping actor
@@ -49,7 +49,7 @@ public:
     Text::RendererPtr                renderer,
     Text::ControllerPtr              controller,
     Text::DecoratorPtr               decorator,
-    float                            alignmentOffset,
+    float&                           alignmentOffset,
     Actor&                           renderableActor,
     Actor&                           backgroundActor,
     Toolkit::Control&                stencil,
index fe01e46..b48faa0 100644 (file)
@@ -201,6 +201,28 @@ Toolkit::TextField::InputStyle::Mask ConvertInputStyle(Text::InputStyle::Mask in
   return fieldInputStyleMask;
 }
 
+bool IsHiddenInput(Toolkit::TextField textField)
+{
+  Property::Map hiddenInputSettings = textField.GetProperty<Property::Map>(Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS);
+  auto mode  = hiddenInputSettings.Find(Toolkit::HiddenInput::Property::MODE);
+  if (mode && (mode->Get<int>() != Toolkit::HiddenInput::Mode::HIDE_NONE))
+  {
+    return true;
+  }
+  return false;
+}
+
+char GetSubstituteCharacter(Toolkit::TextField textField)
+{
+  Property::Map hiddenInputSettings = textField.GetProperty<Property::Map>(Toolkit::TextField::Property::HIDDEN_INPUT_SETTINGS);
+  auto substChar = hiddenInputSettings.Find(Toolkit::HiddenInput::Property::SUBSTITUTE_CHARACTER);
+  if (substChar)
+  {
+    return static_cast<char>(substChar->Get<int>());
+  }
+  return STAR;
+}
+
 } // namespace
 
 Toolkit::TextField TextField::New()
@@ -1101,6 +1123,11 @@ TextField::~TextField()
 std::string TextField::AccessibleImpl::GetName()
 {
   auto self = Toolkit::TextField::DownCast(Self());
+  if (IsHiddenInput(self))
+  {
+    return {};
+  }
+
   return self.GetProperty(Toolkit::TextField::Property::TEXT).Get<std::string>();
 }
 
@@ -1118,7 +1145,10 @@ std::string TextField::AccessibleImpl::GetText(size_t startOffset, size_t endOff
   {
     return {};
   }
-
+  if(IsHiddenInput(self))
+  {
+    return std::string(endOffset - startOffset, GetSubstituteCharacter(self));
+  }
   return text.substr(startOffset, endOffset - startOffset);
 }
 
@@ -1156,11 +1186,18 @@ Dali::Accessibility::Range TextField::AccessibleImpl::GetTextAtOffset(
   size_t offset, Dali::Accessibility::TextBoundary boundary)
 {
   auto self     = Toolkit::TextField::DownCast(Self());
+  auto range    = Dali::Accessibility::Range{};
+
+  if(IsHiddenInput(self))
+  {
+    // Returning empty object, as there is no possibility to parse the textfield
+    // when its content is hidden.
+    return range;
+  }
+
   auto text     = self.GetProperty(Toolkit::TextField::Property::TEXT).Get<std::string>();
   auto textSize = text.size();
 
-  auto range = Dali::Accessibility::Range{};
-
   switch(boundary)
   {
     case Dali::Accessibility::TextBoundary::CHARACTER:
@@ -1252,13 +1289,21 @@ Dali::Accessibility::Range TextField::AccessibleImpl::GetRangeOfSelection(size_t
     return {};
   }
 
-  auto        self       = Toolkit::TextField::DownCast(Self());
-  auto        controller = Dali::Toolkit::GetImpl(self).GetTextController();
-  std::string value{};
-  controller->RetrieveSelection(value);
+  auto self = Toolkit::TextField::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
   auto indices = controller->GetSelectionIndexes();
 
-  return {static_cast<size_t>(indices.first), static_cast<size_t>(indices.second), value};
+  auto startOffset = static_cast<size_t>(indices.first);
+  auto endOffset = static_cast<size_t>(indices.second);
+
+  if (IsHiddenInput(self))
+  {
+    return {startOffset, endOffset, std::string(endOffset - startOffset, GetSubstituteCharacter(self))};
+  }
+
+  std::string value{};
+  controller->RetrieveSelection(value);
+  return {startOffset, endOffset, value};
 }
 
 bool TextField::AccessibleImpl::RemoveSelection(size_t selectionIndex)
index 04a5082..db08d3a 100644 (file)
@@ -507,6 +507,15 @@ void TextField::PropertyHandler::SetProperty(Toolkit::TextField textField, Prope
       if(map)
       {
         impl.mController->SetHiddenInputOption(*map);
+        auto mode = map->Find(Toolkit::HiddenInput::Property::MODE);
+        if(mode && (mode->Get<int>() != Toolkit::HiddenInput::Mode::HIDE_NONE))
+        {
+          textField.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_ROLE, Accessibility::Role::PASSWORD_TEXT);
+        }
+        else
+        {
+          textField.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_ROLE, Accessibility::Role::ENTRY);
+        }
       }
       break;
     }
index 3229006..855df54 100644 (file)
@@ -93,6 +93,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/controls/scrollable/scroll-view/scroll-view-effect-impl.cpp
    ${toolkit_src_dir}/controls/scrollable/scroll-view/scroll-view-impl.cpp
    ${toolkit_src_dir}/controls/scrollable/scroll-view/scroll-view-impl-constraints.cpp
+   ${toolkit_src_dir}/controls/scrollable/scroll-view/scroll-view-impl-property-handler.cpp
    ${toolkit_src_dir}/controls/scrollable/scroll-view/scroll-view-page-path-effect-impl.cpp
    ${toolkit_src_dir}/controls/scene3d-view/scene3d-view-impl.cpp
    ${toolkit_src_dir}/controls/scene3d-view/gltf-loader.cpp
@@ -158,9 +159,11 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/text/text-controller.cpp
    ${toolkit_src_dir}/text/text-controller-event-handler.cpp
    ${toolkit_src_dir}/text/text-controller-impl.cpp
+   ${toolkit_src_dir}/text/text-controller-impl-data-clearer.cpp
    ${toolkit_src_dir}/text/text-controller-impl-event-handler.cpp
    ${toolkit_src_dir}/text/text-controller-impl-model-updater.cpp
    ${toolkit_src_dir}/text/text-controller-input-font-handler.cpp
+   ${toolkit_src_dir}/text/text-controller-input-properties.cpp
    ${toolkit_src_dir}/text/text-controller-placeholder-handler.cpp
    ${toolkit_src_dir}/text/text-controller-relayouter.cpp
    ${toolkit_src_dir}/text/text-controller-text-updater.cpp
@@ -202,6 +205,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/transition-effects/cube-transition-wave-effect-impl.cpp
    ${toolkit_src_dir}/text/xhtml-entities.cpp
    ${toolkit_src_dir}/drag-drop-detector/drag-and-drop-detector-impl.cpp
+   ${toolkit_src_dir}/text/emoji-helper.cpp
 )
 
 SET( SOURCES ${SOURCES}
diff --git a/dali-toolkit/internal/text/emoji-helper.cpp b/dali-toolkit/internal/text/emoji-helper.cpp
new file mode 100644 (file)
index 0000000..eec4ee4
--- /dev/null
@@ -0,0 +1,224 @@
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// FILE HEADER
+#include <dali-toolkit/internal/text/emoji-helper.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+bool IsTextPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                const TextAbstraction::Character& character)
+{
+  return (IsSymbolOrEmojiOrTextScript(currentRunScript) && TextAbstraction::IsTextPresentationSelector(character));
+}
+
+bool IsEmojiPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                 const TextAbstraction::Character& character)
+{
+  return ((IsSymbolOrEmojiScript(currentRunScript) || IsEmojiColorScript(currentRunScript)) &&
+          TextAbstraction::IsEmojiPresentationSelector(character));
+}
+
+bool IsEmojiSequence(const TextAbstraction::Script&    currentRunScript,
+                     const TextAbstraction::Character& character,
+                     const TextAbstraction::Script&    characterScript)
+{
+  return (IsOneOfEmojiScripts(currentRunScript) &&
+          (IsOneOfEmojiScripts(characterScript) ||
+           TextAbstraction::IsZeroWidthJoiner(character) ||
+           TextAbstraction::IsZeroWidthNonJoiner(character) ||
+           TextAbstraction::IsEmojiItem(character) ||
+           TextAbstraction::IsMiscellaneousSymbolsAndArrowsEmoji(character) ||
+           TextAbstraction::IsDingbatsEmoji(character)));
+}
+
+bool IsNewSequence(const Character* const         textBuffer,
+                   const TextAbstraction::Script& currentRunScript,
+                   const Length&                  currentCharacterIndex,
+                   const Length&                  lastCharacterIndex,
+                   TextAbstraction::Script&       currentCharacterScript)
+{
+  // Until now we have two cases : VariationSelector & Keycap
+  // In-case there are more cases then should be added in this function
+
+  return IsNewKeycapSequence(textBuffer, currentCharacterIndex, lastCharacterIndex, currentCharacterScript) ||
+         IsNewVariationSelectorSequence(textBuffer, currentRunScript, currentCharacterIndex, lastCharacterIndex, currentCharacterScript);
+}
+
+//
+
+bool IsNewKeycapSequence(const Character* const   textBuffer,
+                         const Length&            currentCharacterIndex,
+                         const Length&            lastCharacterIndex,
+                         TextAbstraction::Script& currentCharacterScript)
+{
+  // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-sequences.txt Search on "Emoji_Keycap_Sequence"
+  // Ref: https://www.unicode.org/Public/emoji/14.0/emoji-test.txt Search on "subgroup: keycap"
+
+  // Default initialization does not keycap sequence
+  bool isNewKeycapSequence = false;
+
+  if(currentCharacterIndex < lastCharacterIndex)
+  {
+    Character currentCharacter = *(textBuffer + currentCharacterIndex);
+    if(IsStartForKeycapSequence(currentCharacter))
+    {
+      if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
+      {
+        Character characterOne = *(textBuffer + currentCharacterIndex + 1);
+        Character characterTwo = *(textBuffer + currentCharacterIndex + 2);
+
+        if(TextAbstraction::IsEmojiPresentationSelector(characterOne) &&
+           TextAbstraction::IsCombiningEnclosingKeycap(characterTwo))
+        {
+          isNewKeycapSequence    = true;
+          currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+        }
+      } // if(!isNewKeycapSequence && currentCharacterIndex + 2 <= lastCharacterIndex)
+    }   // if(IsStartForKeycapSequence(currentCharacter))
+  }     // if(currentCharacterIndex < lastCharacterIndex)
+
+  return isNewKeycapSequence;
+}
+
+bool IsNewVariationSelectorSequence(const Character* const         textBuffer,
+                                    const TextAbstraction::Script& currentRunScript,
+                                    const Length&                  currentCharacterIndex,
+                                    const Length&                  lastCharacterIndex,
+                                    TextAbstraction::Script&       currentCharacterScript)
+{
+  // Ref: Emoji and Text Presentation Selectors: https://www.unicode.org/reports/tr51/#Emoji_Variation_Selectors
+  // Ref: Emoji Variation Sequences for UTS #51: https://www.unicode.org/Public/14.0.0/ucd/emoji/emoji-variation-sequences.txt
+
+  // Default initialization does not VariationSelector sequence
+  bool isNewVariationSelectorSequence = false;
+
+  if(currentCharacterIndex < lastCharacterIndex)
+  {
+    Character currentCharacter = *(textBuffer + currentCharacterIndex);
+    if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
+    {
+      if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
+      {
+        Character characterVS = *(textBuffer + currentCharacterIndex + 1);
+
+        if(TextAbstraction::IsEmojiPresentationSelector(characterVS))
+        {
+          isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_COLOR;
+          currentCharacterScript         = TextAbstraction::EMOJI_COLOR;
+        }
+        else if(TextAbstraction::IsTextPresentationSelector(characterVS))
+        {
+          isNewVariationSelectorSequence = currentRunScript != TextAbstraction::EMOJI_TEXT;
+          currentCharacterScript         = TextAbstraction::EMOJI_TEXT;
+        }
+
+      } // if(!isNewVariationSelectorSequence && currentCharacterIndex + 1 <= lastCharacterIndex)
+    }   // if(TextAbstraction::IsEmojiVariationSequences(currentCharacter))
+  }     // if(currentCharacterIndex < lastCharacterIndex)
+
+  return isNewVariationSelectorSequence;
+}
+
+bool IsStartForKeycapSequence(const TextAbstraction::Character& character)
+{
+  return (TextAbstraction::IsASCIIDigits(character) ||
+          TextAbstraction::CHAR_NUMBER_SIGN == character ||
+          TextAbstraction::CHAR_ASTERISK == character);
+}
+
+bool IsScriptChangedToFollowSequence(const TextAbstraction::Script&    currentRunScript,
+                                     const TextAbstraction::Character& character,
+                                     TextAbstraction::Script&          currentCharacterScript)
+{
+  bool isUpdated = false;
+
+  // Keycap cases
+  if(TextAbstraction::IsCombiningEnclosingKeycap(character))
+  {
+    if(TextAbstraction::EMOJI == currentRunScript)
+    {
+      // Keycap and unqualified
+      // Emoji request a default presentation for an emoji character.
+      isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
+      currentCharacterScript = TextAbstraction::EMOJI;
+    }
+    else if(TextAbstraction::EMOJI_COLOR == currentRunScript)
+    {
+      // Keycap and fully-qualified
+      // Emoji request an emoji presentation for an emoji character.
+      isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+      currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+    }
+  }
+  // Emoji(Text) Presentation cases
+  else if(IsTextPresentationSequence(currentRunScript, character))
+  {
+    // Emoji request a text presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_TEXT);
+    currentCharacterScript = TextAbstraction::EMOJI_TEXT;
+  }
+  // Emoji(Color) Presentation cases
+  else if(IsEmojiPresentationSequence(currentRunScript, character))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+    currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+  }
+  // Default Emoji
+  else if(IsEmojiScript(currentRunScript) && IsEmojiScript(currentCharacterScript))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI);
+    currentCharacterScript = TextAbstraction::EMOJI;
+  }
+  // Emoji sequences
+  else if(IsEmojiSequence(currentRunScript, character, currentCharacterScript))
+  {
+    // Emoji request an emoji presentation for an emoji character.
+    isUpdated              = (currentCharacterScript != TextAbstraction::EMOJI_COLOR);
+    currentCharacterScript = TextAbstraction::EMOJI_COLOR;
+  }
+
+  return isUpdated;
+}
+
+Character GetVariationSelectorByScript(const TextAbstraction::Script& script)
+{
+  Character character = 0u;
+  if(TextAbstraction::EMOJI_COLOR == script)
+  {
+    character = TextAbstraction::CHAR_VARIATION_SELECTOR_16;
+  }
+  else if(TextAbstraction::EMOJI_TEXT == script)
+  {
+    character = TextAbstraction::CHAR_VARIATION_SELECTOR_15;
+  }
+
+  return character;
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
\ No newline at end of file
diff --git a/dali-toolkit/internal/text/emoji-helper.h b/dali-toolkit/internal/text/emoji-helper.h
new file mode 100644 (file)
index 0000000..95efd9f
--- /dev/null
@@ -0,0 +1,170 @@
+#ifndef DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
+#define DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-definitions.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/text-abstraction/emoji-character-properties.h>
+#include <dali/devel-api/text-abstraction/emoji-helper.h>
+#include <dali/devel-api/text-abstraction/script.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Text
+{
+/**
+ * @brief Whether the sequence is a variation sequence consisting of an emoji character followed by a text presentation selector.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ *
+ * @return @e true if the sequence is text presentation sequence.
+ */
+bool IsTextPresentationSequence(const TextAbstraction::Script& currentRunScript, const TextAbstraction::Character& character);
+
+/**
+ * @brief Whether the sequence is a variation sequence consisting of an emoji character followed by a emoji presentation selector.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ *
+ * @return @e true if the sequence is emoji presentation sequence.
+ */
+bool IsEmojiPresentationSequence(const TextAbstraction::Script&    currentRunScript,
+                                 const TextAbstraction::Character& character);
+
+/**
+ * @brief Whether the sequence is an emoji sequence.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[in] characterScript The script of the next character.
+ *
+ * @return @e true if the sequence is an emoji sequence.
+ */
+bool IsEmojiSequence(const TextAbstraction::Script&    currentRunScript,
+                     const TextAbstraction::Character& character,
+                     const TextAbstraction::Script&    characterScript);
+
+/**
+ * @brief Whether the sequence is a keycap sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update it in-case it's the Keycap sequence.
+ *
+ * @return @e true if @p currentRunScript is ASCII_DIGITS and @p character is COMBINING_ENCLOSING_KEYCAP
+ */
+bool IsNewKeycapSequence(const Character* const   textBuffer,
+                         const Length&            currentCharacterIndex,
+                         const Length&            lastCharacterIndex,
+                         TextAbstraction::Script& currentCharacterScript);
+
+/**
+ * @brief Whether the sequence is a variation selector sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update it in-case it's the VariationSelector sequence.
+ *
+ * @return @e true if @p currentRunScript is ASCII_DIGITS and @p character is COMBINING_ENCLOSING_KEYCAP
+ */
+bool IsNewVariationSelectorSequence(const Character* const         textBuffer,
+                                    const TextAbstraction::Script& currentRunScript,
+                                    const Length&                  currentCharacterIndex,
+                                    const Length&                  lastCharacterIndex,
+                                    TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Whether the case is a new sequence and set script according to the case.
+ *
+ * @param[in] textBuffer The text.
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] currentCharacterIndex The index of current character.
+ * @param[in] lastCharacterIndex The index of last character.
+ * @param[out] currentCharacterScript The current character script to update according to sequence.
+ *
+ * @return @e true the case is a new sequence
+ */
+bool IsNewSequence(const Character* const         textBuffer,
+                   const TextAbstraction::Script& currentRunScript,
+                   const Length&                  currentCharacterIndex,
+                   const Length&                  lastCharacterIndex,
+                   TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Whether the character is ASCII digits | # Number Sign | * Asterisk.
+ *
+ * @param[in] character The character.
+ *
+ * @return @e true if the character is ASCII digits | # Number Sign | * Asterisk.
+ */
+bool IsStartForKeycapSequence(const TextAbstraction::Character& character);
+
+/**
+ * @brief Check sequence case and update script of character if needed.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[out] script The current character script to update according to sequence.
+ *
+ * @return @e true if the script is changed
+ */
+bool IsScriptChangedToFollowSequence(const TextAbstraction::Script&    currentRunScript,
+                                     const TextAbstraction::Character& character,
+                                     TextAbstraction::Script&          script);
+
+/**
+ * @brief Check sequence case and update script of character if needed.
+ *
+ * @param[in] currentRunScript The script of current run.
+ * @param[in] character The next character.
+ * @param[out] script The current character script to update according to sequence.
+ *
+ * @return @e true if the script is changed
+ */
+bool HandleEmojiSequence(const Character* const         textBuffer,
+                         const Length&                  currentCharacterIndex,
+                         const Length&                  lastCharacterIndex,
+                         const TextAbstraction::Script& currentRunScript,
+                         TextAbstraction::Script&       currentCharacterScript);
+
+/**
+ * @brief Determine the Variation Selector according to script.
+ *
+ * @param[in] script The script.
+ *
+ * @return CHAR_VARIATION_SELECTOR_15 in-case EMOJI_TEXT or CHAR_VARIATION_SELECTOR_16 in-case EMOJI_COLOR or 0 otherwise
+ */
+Character GetVariationSelectorByScript(const TextAbstraction::Script& script);
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_EMOJI_HELPER_H
\ No newline at end of file
index 1adbe83..ab2aba5 100644 (file)
@@ -23,7 +23,6 @@
 
 using namespace Dali::Toolkit;
 
-const uint32_t STAR                  = 0x2A; // Set default substitute character as '*'
 const int      DEFAULT_SHOW_DURATION = 1000;
 
 namespace Dali
index 70d4a82..b13c263 100644 (file)
@@ -32,6 +32,9 @@ namespace Toolkit
 {
 namespace Text
 {
+
+static constexpr const uint32_t STAR = 0x2A; // Set default substitute character as '*'
+
 /**
  * Class to handle the hidden text
  */
index 82470f8..dbd110b 100644 (file)
@@ -24,6 +24,7 @@
 #include <dali/integration-api/debug.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/emoji-helper.h>
 #include <dali-toolkit/internal/text/multi-language-helper-functions.h>
 
 namespace Dali
@@ -97,11 +98,11 @@ MultilanguageSupport::MultilanguageSupport()
 {
   // Initializes the default font cache to zero (invalid font).
   // Reserves space to cache the default fonts and access them with the script as an index.
-  mDefaultFontPerScriptCache.Resize(TextAbstraction::UNKNOWN + 1, NULL);
+  mDefaultFontPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
 
   // Initializes the valid fonts cache to NULL (no valid fonts).
   // Reserves space to cache the valid fonts and access them with the script as an index.
-  mValidFontsPerScriptCache.Resize(TextAbstraction::UNKNOWN + 1, NULL);
+  mValidFontsPerScriptCache.Resize(TextAbstraction::GetNumberOfScripts(), NULL);
 }
 
 MultilanguageSupport::~MultilanguageSupport()
@@ -205,6 +206,7 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
 
   // Traverse all characters and set the scripts.
   const Length lastCharacter = startIndex + numberOfCharacters;
+
   for(Length index = startIndex; index < lastCharacter; ++index)
   {
     Character character = *(textBuffer + index);
@@ -222,26 +224,40 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
 
     // Skip those characters valid for many scripts like white spaces or '\n'.
     bool endOfText = index == lastCharacter;
+
+    //Handle all Emoji Sequence cases
+    if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
+    {
+      AddCurrentScriptAndCreatNewScript(script,
+                                        false,
+                                        false,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
+    }
+    else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
+    {
+      currentScriptRun.script = script;
+    }
+    else if(IsOneOfEmojiScripts(currentScriptRun.script) && (TextAbstraction::COMMON == script))
+    {
+      // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
+      AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
+                                        false,
+                                        false,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
+    }
+
     while(!endOfText &&
           (TextAbstraction::COMMON == script))
     {
       // Check if whether is right to left markup and Keeps true if the previous value was true.
       currentScriptRun.isRightToLeft = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftMark(character);
 
-      // ZWJ, ZWNJ between emojis should be treated as EMOJI.
-      if(TextAbstraction::EMOJI == currentScriptRun.script && !(TextAbstraction::IsZeroWidthJoiner(character) || TextAbstraction::IsZeroWidthNonJoiner(character)))
-      {
-        // Emojis doesn't mix well with characters common to all scripts. Insert the emoji run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-
-        // Initialize the new one.
-        currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-        currentScriptRun.characterRun.numberOfCharacters = 0u;
-        currentScriptRun.script                          = TextAbstraction::UNKNOWN;
-        numberOfAllScriptCharacters                      = 0u;
-      }
-
       // Count all these characters to be added into a script.
       ++numberOfAllScriptCharacters;
 
@@ -253,20 +269,13 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
         // the same direction than the first script of the paragraph.
         isFirstScriptToBeSet = true;
 
-        // Characters common to all scripts at the end of the paragraph are added to the last script.
-        currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
-
-        // Store the script run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-
-        // Initialize the new one.
-        currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-        currentScriptRun.characterRun.numberOfCharacters = 0u;
-        currentScriptRun.script                          = TextAbstraction::UNKNOWN;
-        numberOfAllScriptCharacters                      = 0u;
-        // Initialize whether is right to left direction
-        currentScriptRun.isRightToLeft = false;
+        AddCurrentScriptAndCreatNewScript(TextAbstraction::UNKNOWN,
+                                          false,
+                                          false,
+                                          currentScriptRun,
+                                          numberOfAllScriptCharacters,
+                                          scripts,
+                                          scriptIndex);
       }
 
       // Get the next character.
@@ -276,6 +285,22 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
       {
         character = *(textBuffer + index);
         script    = TextAbstraction::GetCharacterScript(character);
+
+        //Handle all Emoji Sequence cases
+        if(IsNewSequence(textBuffer, currentScriptRun.script, index, lastCharacter, script))
+        {
+          AddCurrentScriptAndCreatNewScript(script,
+                                            false,
+                                            false,
+                                            currentScriptRun,
+                                            numberOfAllScriptCharacters,
+                                            scripts,
+                                            scriptIndex);
+        }
+        else if(IsScriptChangedToFollowSequence(currentScriptRun.script, character, script))
+        {
+          currentScriptRun.script = script;
+        }
       }
     } // end while( !endOfText && ( TextAbstraction::COMMON == script ) )
 
@@ -290,7 +315,10 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
     if(isFirstScriptToBeSet &&
        (TextAbstraction::UNKNOWN != script) &&
        (TextAbstraction::COMMON != script) &&
-       (TextAbstraction::EMOJI != script))
+       (TextAbstraction::EMOJI != script) &&
+       (TextAbstraction::EMOJI_TEXT != script) &&
+       (TextAbstraction::EMOJI_COLOR != script) &&
+       (!TextAbstraction::IsSymbolScript(script)))
     {
       // Sets the direction of the first valid script.
       isParagraphRTL       = currentScriptRun.isRightToLeft || TextAbstraction::IsRightToLeftScript(script);
@@ -319,26 +347,21 @@ void MultilanguageSupport::SetScripts(const Vector<Character>& text,
         numberOfAllScriptCharacters = 0u;
       }
       else if((TextAbstraction::UNKNOWN == currentScriptRun.script) &&
-              (TextAbstraction::EMOJI == script))
+              (TextAbstraction::IsSymbolOrEmojiOrTextScript(script)))
       {
         currentScriptRun.characterRun.numberOfCharacters += numberOfAllScriptCharacters;
         numberOfAllScriptCharacters = 0u;
       }
 
-      if(0u != currentScriptRun.characterRun.numberOfCharacters)
-      {
-        // Store the script run.
-        scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
-        ++scriptIndex;
-      }
-
-      // Initialize the new one.
-      currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
-      currentScriptRun.characterRun.numberOfCharacters = numberOfAllScriptCharacters + 1u; // Adds the white spaces which are at the begining of the script.
-      currentScriptRun.script                          = script;
-      numberOfAllScriptCharacters                      = 0u;
-      // Check if whether is right to left script.
-      currentScriptRun.isRightToLeft = TextAbstraction::IsRightToLeftScript(currentScriptRun.script);
+      // Adds the white spaces which are at the begining of the script.
+      numberOfAllScriptCharacters++;
+      AddCurrentScriptAndCreatNewScript(script,
+                                        TextAbstraction::IsRightToLeftScript(script),
+                                        true,
+                                        currentScriptRun,
+                                        numberOfAllScriptCharacters,
+                                        scripts,
+                                        scriptIndex);
     }
     else
     {
@@ -445,8 +468,8 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   Vector<ScriptRun>::ConstIterator scriptRunEndIt          = scripts.End();
   bool                             isNewParagraphCharacter = false;
 
-  bool   isPreviousEmojiScript = false;
-  FontId previousEmojiFontId   = 0u;
+  FontId                  previousEmojiFontId = 0u;
+  TextAbstraction::Script previousScript      = TextAbstraction::UNKNOWN;
 
   CharacterIndex lastCharacter = startIndex + numberOfCharacters;
   for(Length index = startIndex; index < lastCharacter; ++index)
@@ -518,28 +541,11 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
     }
 
     bool isCommonScript = false;
-    bool isEmojiScript  = TextAbstraction::EMOJI == script;
-
-    if(isEmojiScript && !isPreviousEmojiScript)
-    {
-      if(0u != currentFontRun.characterRun.numberOfCharacters)
-      {
-        // Store the font run.
-        fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
-        ++fontIndex;
-      }
-
-      // Initialize the new one.
-      currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
-      currentFontRun.characterRun.numberOfCharacters = 0u;
-      currentFontRun.fontId                          = fontId;
-      currentFontRun.isItalicRequired                = false;
-      currentFontRun.isBoldRequired                  = false;
-    }
+    bool isEmojiScript  = TextAbstraction::IsEmojiScript(script) || TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script);
 
-    // ZWJ, ZWNJ between emojis should use the previous emoji font.
-    if(isEmojiScript && (TextAbstraction::IsZeroWidthJoiner(character) || TextAbstraction::IsZeroWidthNonJoiner(character)))
+    if(isEmojiScript && (previousScript == script))
     {
+      // Emoji sequence should use the previous emoji font.
       if(0u != previousEmojiFontId)
       {
         fontId      = previousEmojiFontId;
@@ -567,7 +573,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       //
       //      Many fonts support 'white spaces' so probably the font set by the user or the platform's default
       //      supports the 'white space'. However, that font may not support the DEVANAGARI script.
-      isCommonScript = TextAbstraction::IsCommonScript(character);
+      isCommonScript = TextAbstraction::IsCommonScript(character) || TextAbstraction::IsEmojiPresentationSelector(character);
 
       // Check in the valid fonts cache.
       ValidateFontsPerScript* validateFontsPerScript = *(validFontsPerScriptCacheBuffer + script);
@@ -659,14 +665,55 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
       }   // !isValidFont (2)
     }     // !isValidFont (1)
 
+    if(isEmojiScript && (previousScript != script))
+    {
+      //New Emoji sequence should select font according to the variation selector (VS15 or VS16).
+      if(0u != currentFontRun.characterRun.numberOfCharacters)
+      {
+        // Store the font run.
+        fonts.Insert(fonts.Begin() + fontIndex, currentFontRun);
+        ++fontIndex;
+      }
+
+      // Initialize the new one.
+      currentFontRun.characterRun.characterIndex     = currentFontRun.characterRun.characterIndex + currentFontRun.characterRun.numberOfCharacters;
+      currentFontRun.characterRun.numberOfCharacters = 0u;
+      currentFontRun.fontId                          = fontId;
+      currentFontRun.isItalicRequired                = false;
+      currentFontRun.isBoldRequired                  = false;
+
+      if(TextAbstraction::IsEmojiColorScript(script) || TextAbstraction::IsEmojiTextScript(script))
+      {
+        bool       isModifiedByVariationSelector = false;
+        GlyphIndex glyphIndexChar                = fontClient.GetGlyphIndex(fontId, character);
+        GlyphIndex glyphIndexCharByVS            = fontClient.GetGlyphIndex(fontId, character, Text::GetVariationSelectorByScript(script));
+
+        isModifiedByVariationSelector = glyphIndexChar != glyphIndexCharByVS;
+
+        if(isModifiedByVariationSelector)
+        {
+          FontId requestedFontId = fontClient.FindDefaultFont(character, currentFontPointSize, IsEmojiColorScript(script));
+          if(0u != requestedFontId)
+          {
+            currentFontRun.fontId = fontId = requestedFontId;
+            isValidFont                    = true;
+          }
+        }
+      }
+    }
+
     // Store the font id when the first character is an emoji.
-    if(isEmojiScript && !isPreviousEmojiScript)
+    if(isEmojiScript)
     {
-      if(0u != fontId)
+      if(0u != fontId && previousScript != script)
       {
         previousEmojiFontId = fontId;
       }
     }
+    else
+    {
+      previousEmojiFontId = 0u;
+    }
 
 #ifdef DEBUG_ENABLED
     {
@@ -715,7 +762,7 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
 
     // Whether the current character is a new paragraph character.
     isNewParagraphCharacter = TextAbstraction::IsNewParagraph(character);
-    isPreviousEmojiScript   = isEmojiScript;
+    previousScript          = script;
   } // end traverse characters.
 
   if(0u != currentFontRun.characterRun.numberOfCharacters)
@@ -746,6 +793,34 @@ void MultilanguageSupport::ValidateFonts(const Vector<Character>&
   DALI_LOG_INFO(gLogFilter, Debug::General, "<--MultilanguageSupport::ValidateFonts\n");
 }
 
+void MultilanguageSupport::AddCurrentScriptAndCreatNewScript(const Script       requestedScript,
+                                                             const bool         isRightToLeft,
+                                                             const bool         addScriptCharactersToNewScript,
+                                                             ScriptRun&         currentScriptRun,
+                                                             Length&            numberOfAllScriptCharacters,
+                                                             Vector<ScriptRun>& scripts,
+                                                             ScriptRunIndex&    scriptIndex)
+{
+  // Add the pending characters to the current script
+  currentScriptRun.characterRun.numberOfCharacters += (addScriptCharactersToNewScript ? 0u : numberOfAllScriptCharacters);
+
+  // In-case the current script is empty then no need to add it for scripts
+  if(0u != currentScriptRun.characterRun.numberOfCharacters)
+  {
+    // Store the script run.
+    scripts.Insert(scripts.Begin() + scriptIndex, currentScriptRun);
+    ++scriptIndex;
+  }
+
+  // Initialize the new one by the requested script
+  currentScriptRun.characterRun.characterIndex     = currentScriptRun.characterRun.characterIndex + currentScriptRun.characterRun.numberOfCharacters;
+  currentScriptRun.characterRun.numberOfCharacters = (addScriptCharactersToNewScript ? numberOfAllScriptCharacters : 0u);
+  currentScriptRun.script                          = requestedScript;
+  numberOfAllScriptCharacters                      = 0u;
+  // Initialize whether is right to left direction
+  currentScriptRun.isRightToLeft = isRightToLeft;
+}
+
 } // namespace Internal
 
 } // namespace Text
index de44e19..cda767a 100644 (file)
@@ -160,6 +160,28 @@ public:
 private:
   Vector<DefaultFonts*>           mDefaultFontPerScriptCache; ///< Caches default fonts for a script.
   Vector<ValidateFontsPerScript*> mValidFontsPerScriptCache;  ///< Caches valid fonts for a script.
+
+  //Methods
+
+  /**
+ * @brief Add the current script to scripts and create new script.
+ *
+ * @param[in] requestedScript The script of the new script run.
+ * @param[in] isRightToLeft The direction of the new script run.
+ * @param[in] addScriptCharactersToNewScript Whether to add the pending characters to the new script run or to the current script run.
+ * @param[inout] currentScriptRun The current character script run and it will be updated it to the new script run.
+ * @param[inout] numberOfAllScriptCharacters The pending characters.
+ * @param[inout] scripts The list of scripts.
+ * @param[inout] scriptIndex The current index of scripts.
+ *
+ */
+  void AddCurrentScriptAndCreatNewScript(const Script       requestedScript,
+                                         const bool         isRightToLeft,
+                                         const bool         addScriptCharactersToNewScript,
+                                         ScriptRun&         currentScriptRun,
+                                         Length&            numberOfAllScriptCharacters,
+                                         Vector<ScriptRun>& scripts,
+                                         ScriptRunIndex&    scriptIndex);
 };
 
 } // namespace Internal
diff --git a/dali-toolkit/internal/text/text-controller-impl-data-clearer.cpp b/dali-toolkit/internal/text/text-controller-impl-data-clearer.cpp
new file mode 100644 (file)
index 0000000..87099da
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-controller-impl.h>
+#include <dali-toolkit/internal/text/text-run-container.h>
+
+namespace Dali::Toolkit::Text
+{
+
+void ControllerImplDataClearer::ClearFullModelData(Controller::Impl& impl, Controller::OperationsMask operations)
+{
+  ModelPtr& model = impl.mModel;
+
+  if(Controller::NO_OPERATION != (Controller::GET_LINE_BREAKS & operations))
+  {
+    model->mLogicalModel->mLineBreakInfo.Clear();
+    model->mLogicalModel->mParagraphInfo.Clear();
+  }
+
+  if(Controller::NO_OPERATION != (Controller::GET_SCRIPTS & operations))
+  {
+    model->mLogicalModel->mScriptRuns.Clear();
+  }
+
+  if(Controller::NO_OPERATION != (Controller::VALIDATE_FONTS & operations))
+  {
+    model->mLogicalModel->mFontRuns.Clear();
+  }
+
+  if(0u != model->mLogicalModel->mBidirectionalParagraphInfo.Count())
+  {
+    if(Controller::NO_OPERATION != (Controller::BIDI_INFO & operations))
+    {
+      model->mLogicalModel->mBidirectionalParagraphInfo.Clear();
+      model->mLogicalModel->mCharacterDirections.Clear();
+    }
+
+    if(Controller::NO_OPERATION != (Controller::REORDER & operations))
+    {
+      // Free the allocated memory used to store the conversion table in the bidirectional line info run.
+      for(Vector<BidirectionalLineInfoRun>::Iterator it    = model->mLogicalModel->mBidirectionalLineInfo.Begin(),
+                                                     endIt = model->mLogicalModel->mBidirectionalLineInfo.End();
+          it != endIt;
+          ++it)
+      {
+        BidirectionalLineInfoRun& bidiLineInfo = *it;
+
+        free(bidiLineInfo.visualToLogicalMap);
+        bidiLineInfo.visualToLogicalMap = NULL;
+      }
+      model->mLogicalModel->mBidirectionalLineInfo.Clear();
+    }
+  }
+
+  if(Controller::NO_OPERATION != (Controller::SHAPE_TEXT & operations))
+  {
+    model->mVisualModel->mGlyphs.Clear();
+    model->mVisualModel->mGlyphsToCharacters.Clear();
+    model->mVisualModel->mCharactersToGlyph.Clear();
+    model->mVisualModel->mCharactersPerGlyph.Clear();
+    model->mVisualModel->mGlyphsPerCharacter.Clear();
+    model->mVisualModel->mGlyphPositions.Clear();
+  }
+
+  if(Controller::NO_OPERATION != (Controller::LAYOUT & operations))
+  {
+    model->mVisualModel->mLines.Clear();
+  }
+
+  if(Controller::NO_OPERATION != (Controller::COLOR & operations))
+  {
+    model->mVisualModel->mColorIndices.Clear();
+    model->mVisualModel->mBackgroundColorIndices.Clear();
+  }
+}
+
+void ControllerImplDataClearer::ClearCharacterModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations)
+{
+  const CharacterIndex endIndexPlusOne = endIndex + 1u;
+  ModelPtr& model = impl.mModel;
+
+  if(Controller::NO_OPERATION != (Controller::GET_LINE_BREAKS & operations))
+  {
+    // Clear the line break info.
+    LineBreakInfo* lineBreakInfoBuffer = model->mLogicalModel->mLineBreakInfo.Begin();
+
+    model->mLogicalModel->mLineBreakInfo.Erase(lineBreakInfoBuffer + startIndex,
+                                                     lineBreakInfoBuffer + endIndexPlusOne);
+
+    // Clear the paragraphs.
+    ClearCharacterRuns(startIndex,
+                       endIndex,
+                       model->mLogicalModel->mParagraphInfo);
+  }
+
+  if(Controller::NO_OPERATION != (Controller::GET_SCRIPTS & operations))
+  {
+    // Clear the scripts.
+    ClearCharacterRuns(startIndex,
+                       endIndex,
+                       model->mLogicalModel->mScriptRuns);
+  }
+
+  if(Controller::NO_OPERATION != (Controller::VALIDATE_FONTS & operations))
+  {
+    // Clear the fonts.
+    ClearCharacterRuns(startIndex,
+                       endIndex,
+                       model->mLogicalModel->mFontRuns);
+  }
+
+  if(0u != model->mLogicalModel->mBidirectionalParagraphInfo.Count())
+  {
+    if(Controller::NO_OPERATION != (Controller::BIDI_INFO & operations))
+    {
+      // Clear the bidirectional paragraph info.
+      ClearCharacterRuns(startIndex,
+                         endIndex,
+                         model->mLogicalModel->mBidirectionalParagraphInfo);
+
+      // Clear the character's directions.
+      CharacterDirection* characterDirectionsBuffer = model->mLogicalModel->mCharacterDirections.Begin();
+
+      model->mLogicalModel->mCharacterDirections.Erase(characterDirectionsBuffer + startIndex,
+                                                       characterDirectionsBuffer + endIndexPlusOne);
+    }
+
+    if(Controller::NO_OPERATION != (Controller::REORDER & operations))
+    {
+      uint32_t startRemoveIndex = model->mLogicalModel->mBidirectionalLineInfo.Count();
+      uint32_t endRemoveIndex   = startRemoveIndex;
+      ClearCharacterRuns(startIndex,
+                         endIndex,
+                         model->mLogicalModel->mBidirectionalLineInfo,
+                         startRemoveIndex,
+                         endRemoveIndex);
+
+      BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = model->mLogicalModel->mBidirectionalLineInfo.Begin();
+
+      // Free the allocated memory used to store the conversion table in the bidirectional line info run.
+      for(Vector<BidirectionalLineInfoRun>::Iterator it    = bidirectionalLineInfoBuffer + startRemoveIndex,
+                                                     endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
+          it != endIt;
+          ++it)
+      {
+        BidirectionalLineInfoRun& bidiLineInfo = *it;
+
+        free(bidiLineInfo.visualToLogicalMap);
+        bidiLineInfo.visualToLogicalMap = NULL;
+      }
+
+      model->mLogicalModel->mBidirectionalLineInfo.Erase(bidirectionalLineInfoBuffer + startRemoveIndex,
+                                                         bidirectionalLineInfoBuffer + endRemoveIndex);
+    }
+  }
+}
+
+void ControllerImplDataClearer::ClearGlyphModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations)
+{
+  const CharacterIndex endIndexPlusOne           = endIndex + 1u;
+  const Length         numberOfCharactersRemoved = endIndexPlusOne - startIndex;
+  ModelPtr&            model                     = impl.mModel;
+  TextUpdateInfo&      textUpdateInfo            = impl.mTextUpdateInfo;
+
+
+  // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
+  GlyphIndex* charactersToGlyphBuffer  = model->mVisualModel->mCharactersToGlyph.Begin();
+  Length*     glyphsPerCharacterBuffer = model->mVisualModel->mGlyphsPerCharacter.Begin();
+
+  const GlyphIndex endGlyphIndexPlusOne  = *(charactersToGlyphBuffer + endIndex) + *(glyphsPerCharacterBuffer + endIndex);
+  const Length     numberOfGlyphsRemoved = endGlyphIndexPlusOne - textUpdateInfo.mStartGlyphIndex;
+
+  if(Controller::NO_OPERATION != (Controller::SHAPE_TEXT & operations))
+  {
+    // Update the character to glyph indices.
+    for(Vector<GlyphIndex>::Iterator it    = charactersToGlyphBuffer + endIndexPlusOne,
+                                     endIt = charactersToGlyphBuffer + model->mVisualModel->mCharactersToGlyph.Count();
+        it != endIt;
+        ++it)
+    {
+      CharacterIndex& index = *it;
+      index -= numberOfGlyphsRemoved;
+    }
+
+    // Clear the character to glyph conversion table.
+    model->mVisualModel->mCharactersToGlyph.Erase(charactersToGlyphBuffer + startIndex,
+                                                  charactersToGlyphBuffer + endIndexPlusOne);
+
+    // Clear the glyphs per character table.
+    model->mVisualModel->mGlyphsPerCharacter.Erase(glyphsPerCharacterBuffer + startIndex,
+                                                   glyphsPerCharacterBuffer + endIndexPlusOne);
+
+    // Clear the glyphs buffer.
+    GlyphInfo* glyphsBuffer = model->mVisualModel->mGlyphs.Begin();
+    model->mVisualModel->mGlyphs.Erase(glyphsBuffer + textUpdateInfo.mStartGlyphIndex,
+                                       glyphsBuffer + endGlyphIndexPlusOne);
+
+    CharacterIndex* glyphsToCharactersBuffer = model->mVisualModel->mGlyphsToCharacters.Begin();
+
+    // Update the glyph to character indices.
+    for(Vector<CharacterIndex>::Iterator it    = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
+                                         endIt = glyphsToCharactersBuffer + model->mVisualModel->mGlyphsToCharacters.Count();
+        it != endIt;
+        ++it)
+    {
+      CharacterIndex& index = *it;
+      index -= numberOfCharactersRemoved;
+    }
+
+    // Clear the glyphs to characters buffer.
+    model->mVisualModel->mGlyphsToCharacters.Erase(glyphsToCharactersBuffer + textUpdateInfo.mStartGlyphIndex,
+                                                   glyphsToCharactersBuffer + endGlyphIndexPlusOne);
+
+    // Clear the characters per glyph buffer.
+    Length* charactersPerGlyphBuffer = model->mVisualModel->mCharactersPerGlyph.Begin();
+    model->mVisualModel->mCharactersPerGlyph.Erase(charactersPerGlyphBuffer + textUpdateInfo.mStartGlyphIndex,
+                                                   charactersPerGlyphBuffer + endGlyphIndexPlusOne);
+
+    // Should pass if mGlyphPositions has already been cleared in Controller::Relayouter::Relayout
+    if(0u != model->mVisualModel->mGlyphPositions.Count())
+    {
+      // Clear the positions buffer.
+      Vector2* positionsBuffer = model->mVisualModel->mGlyphPositions.Begin();
+      model->mVisualModel->mGlyphPositions.Erase(positionsBuffer + textUpdateInfo.mStartGlyphIndex,
+                                                 positionsBuffer + endGlyphIndexPlusOne);
+    }
+  }
+
+  if(Controller::NO_OPERATION != (Controller::LAYOUT & operations))
+  {
+    // Clear the lines.
+    uint32_t startRemoveIndex = model->mVisualModel->mLines.Count();
+    uint32_t endRemoveIndex   = startRemoveIndex;
+    ClearCharacterRuns(startIndex,
+                       endIndex,
+                       model->mVisualModel->mLines,
+                       startRemoveIndex,
+                       endRemoveIndex);
+
+    // Will update the glyph runs.
+    startRemoveIndex = model->mVisualModel->mLines.Count();
+    endRemoveIndex   = startRemoveIndex;
+    ClearGlyphRuns(textUpdateInfo.mStartGlyphIndex,
+                   endGlyphIndexPlusOne - 1u,
+                   model->mVisualModel->mLines,
+                   startRemoveIndex,
+                   endRemoveIndex);
+
+    // Set the line index from where to insert the new laid-out lines.
+    textUpdateInfo.mStartLineIndex = startRemoveIndex;
+
+    LineRun* linesBuffer = model->mVisualModel->mLines.Begin();
+    model->mVisualModel->mLines.Erase(linesBuffer + startRemoveIndex,
+                                      linesBuffer + endRemoveIndex);
+  }
+
+  if(Controller::NO_OPERATION != (Controller::COLOR & operations))
+  {
+    if(0u != model->mVisualModel->mColorIndices.Count())
+    {
+      ColorIndex* colorIndexBuffer = model->mVisualModel->mColorIndices.Begin();
+      model->mVisualModel->mColorIndices.Erase(colorIndexBuffer + textUpdateInfo.mStartGlyphIndex,
+                                                colorIndexBuffer + endGlyphIndexPlusOne);
+    }
+
+    if(0u != model->mVisualModel->mBackgroundColorIndices.Count())
+    {
+      ColorIndex* backgroundColorIndexBuffer = model->mVisualModel->mBackgroundColorIndices.Begin();
+      model->mVisualModel->mBackgroundColorIndices.Erase(backgroundColorIndexBuffer + textUpdateInfo.mStartGlyphIndex,
+                                                         backgroundColorIndexBuffer + endGlyphIndexPlusOne);
+    }
+  }
+}
+
+void ControllerImplDataClearer::ClearModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations)
+{
+  TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
+
+  if(textUpdateInfo.mClearAll ||
+     ((0u == startIndex) &&
+      (textUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u)))
+  {
+    ClearFullModelData(impl, operations);
+  }
+  else
+  {
+    // Clear the model data related with characters.
+    ClearCharacterModelData(impl, startIndex, endIndex, operations);
+
+    // Clear the model data related with glyphs.
+    ClearGlyphModelData(impl, startIndex, endIndex, operations);
+  }
+
+  ModelPtr& model = impl.mModel;
+
+  // The estimated number of lines. Used to avoid reallocations when layouting.
+  textUpdateInfo.mEstimatedNumberOfLines = std::max(model->mVisualModel->mLines.Count(), model->mLogicalModel->mParagraphInfo.Count());
+
+  model->mVisualModel->ClearCaches();
+}
+
+} // namespace Dali::Toolkit::Text
diff --git a/dali-toolkit/internal/text/text-controller-impl-data-clearer.h b/dali-toolkit/internal/text/text-controller-impl-data-clearer.h
new file mode 100644 (file)
index 0000000..87d6e24
--- /dev/null
@@ -0,0 +1,81 @@
+#ifndef DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_DATA_CLEARER_H
+#define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_DATA_CLEARER_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-controller.h>
+
+namespace Dali::Toolkit::Text
+{
+
+/// Provides methods to clear some of the model data in the Text::Controller::Impl
+struct ControllerImplDataClearer
+{
+
+  /**
+   * @brief Helper to clear completely the parts of the model specified by the given @p operations.
+   *
+   * @note It never clears the text stored in utf32.
+   *
+   * @param[in] impl The text controller impl.
+   * @param[in] operations The operations required.
+   */
+  static void ClearFullModelData(Controller::Impl& impl, Controller::OperationsMask operations);
+
+  /**
+   * @brief Helper to clear completely the parts of the model related with the characters specified by the given @p operations.
+   *
+   * @note It never clears the text stored in utf32.
+   *
+   * @param[in] impl The text controller impl.
+   * @param[in] startIndex Index to the first character to be cleared.
+   * @param[in] endIndex Index to the last character to be cleared.
+   * @param[in] operations The operations required.
+   */
+  static void ClearCharacterModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations);
+
+  /**
+   * @brief Helper to clear completely the parts of the model related with the glyphs specified by the given @p operations.
+   *
+   * @note It never clears the text stored in utf32.
+   * @note Character indices are transformed to glyph indices.
+   *
+   * @param[in] impl The text controller impl.
+   * @param[in] startIndex Index to the first character to be cleared.
+   * @param[in] endIndex Index to the last character to be cleared.
+   * @param[in] operations The operations required.
+   */
+  static void ClearGlyphModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations);
+
+  /**
+   * @brief Helper to clear the parts of the model specified by the given @p operations and from @p startIndex to @p endIndex.
+   *
+   * @note It never clears the text stored in utf32.
+   *
+   * @param[in] impl The text controller impl.
+   * @param[in] startIndex Index to the first character to be cleared.
+   * @param[in] endIndex Index to the last character to be cleared.
+   * @param[in] operations The operations required.
+   */
+  static void ClearModelData(Controller::Impl& impl, CharacterIndex startIndex, CharacterIndex endIndex, Controller::OperationsMask operations);
+};
+
+} // namespace Dali::Toolkit::Text
+
+#endif // DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_DATA_CLEARER_H
index 2edca46..8a1dd76 100644 (file)
 #include <dali-toolkit/internal/text/text-controller-impl.h>
 
 // EXTERNAL INCLUDES
+#include <cmath>
 #include <dali/integration-api/debug.h>
 #include <dali/public-api/rendering/renderer.h>
+#include <dali/devel-api/adaptor-framework/window-devel.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
@@ -28,6 +30,7 @@
 #include <dali-toolkit/internal/text/character-set-conversion.h>
 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
 #include <dali-toolkit/internal/text/text-control-interface.h>
+#include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
@@ -129,6 +132,243 @@ void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const font
     }
   }
 }
+
+void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
+{
+  EventData* eventData = impl.mEventData;
+
+  if(nullptr == eventData)
+  {
+    // Nothing to do if there is no text input.
+    return;
+  }
+
+  DecoratorPtr& decorator = eventData->mDecorator;
+  if(!decorator)
+  {
+    // Nothing to do if there is no decorator.
+    return;
+  }
+
+  DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d  newstate:%d\n", eventData->mState, newState);
+
+  if(eventData->mState != newState)
+  {
+    eventData->mPreviousState = eventData->mState;
+    eventData->mState         = newState;
+
+    switch(eventData->mState)
+    {
+      case EventData::INACTIVE:
+      {
+        decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
+        decorator->StopCursorBlink();
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+        decorator->SetPopupActive(false);
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::INTERRUPTED:
+      {
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+        decorator->SetPopupActive(false);
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::SELECTING:
+      {
+        decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
+        decorator->StopCursorBlink();
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
+          decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
+        }
+        decorator->SetHighlightActive(true);
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          impl.SetPopupButtons();
+          decorator->SetPopupActive(true);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::EDITING:
+      {
+        decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
+        if(eventData->mCursorBlinkEnabled)
+        {
+          decorator->StartCursorBlink();
+        }
+        // Grab handle is not shown until a tap is received whilst EDITING
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          decorator->SetPopupActive(false);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+      case EventData::EDITING_WITH_POPUP:
+      {
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
+
+        decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
+        if(eventData->mCursorBlinkEnabled)
+        {
+          decorator->StartCursorBlink();
+        }
+        if(eventData->mSelectionEnabled)
+        {
+          decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+          decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+          decorator->SetHighlightActive(false);
+        }
+        else if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(GRAB_HANDLE, true);
+        }
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          impl.SetPopupButtons();
+          decorator->SetPopupActive(true);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+      case EventData::EDITING_WITH_GRAB_HANDLE:
+      {
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
+
+        decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
+        if(eventData->mCursorBlinkEnabled)
+        {
+          decorator->StartCursorBlink();
+        }
+        // Grab handle is not shown until a tap is received whilst EDITING
+        if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(GRAB_HANDLE, true);
+        }
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          decorator->SetPopupActive(false);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::SELECTION_HANDLE_PANNING:
+      {
+        decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
+        decorator->StopCursorBlink();
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
+          decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
+        }
+        decorator->SetHighlightActive(true);
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          decorator->SetPopupActive(false);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::GRAB_HANDLE_PANNING:
+      {
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
+
+        decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
+        if(eventData->mCursorBlinkEnabled)
+        {
+          decorator->StartCursorBlink();
+        }
+        if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(GRAB_HANDLE, true);
+        }
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          decorator->SetPopupActive(false);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::EDITING_WITH_PASTE_POPUP:
+      {
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
+
+        decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
+        if(eventData->mCursorBlinkEnabled)
+        {
+          decorator->StartCursorBlink();
+        }
+
+        if(eventData->mGrabHandleEnabled)
+        {
+          decorator->SetHandleActive(GRAB_HANDLE, true);
+        }
+        decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+        decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+        decorator->SetHighlightActive(false);
+
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          impl.SetPopupButtons();
+          decorator->SetPopupActive(true);
+        }
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+
+      case EventData::TEXT_PANNING:
+      {
+        decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
+        decorator->StopCursorBlink();
+        decorator->SetHandleActive(GRAB_HANDLE, false);
+        if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
+            decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
+        {
+          decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
+          decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
+          decorator->SetHighlightActive(true);
+        }
+
+        if(eventData->mGrabHandlePopupEnabled)
+        {
+          decorator->SetPopupActive(false);
+        }
+
+        eventData->mDecoratorUpdated = true;
+        break;
+      }
+    }
+  }
+}
+
 } // unnamed Namespace
 
 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
@@ -268,6 +508,19 @@ void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
   }
 }
 
+Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
+{
+  if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
+     (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
+  {
+    return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
+  }
+  else
+  {
+    return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
+  }
+}
+
 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
 {
   mTextUpdateInfo.mParagraphCharacterIndex = 0u;
@@ -353,286 +606,9 @@ void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
   mTextUpdateInfo.mStartGlyphIndex             = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
 }
 
-void Controller::Impl::ClearFullModelData(OperationsMask operations)
-{
-  if(NO_OPERATION != (GET_LINE_BREAKS & operations))
-  {
-    mModel->mLogicalModel->mLineBreakInfo.Clear();
-    mModel->mLogicalModel->mParagraphInfo.Clear();
-  }
-
-  if(NO_OPERATION != (GET_SCRIPTS & operations))
-  {
-    mModel->mLogicalModel->mScriptRuns.Clear();
-  }
-
-  if(NO_OPERATION != (VALIDATE_FONTS & operations))
-  {
-    mModel->mLogicalModel->mFontRuns.Clear();
-  }
-
-  if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count())
-  {
-    if(NO_OPERATION != (BIDI_INFO & operations))
-    {
-      mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear();
-      mModel->mLogicalModel->mCharacterDirections.Clear();
-    }
-
-    if(NO_OPERATION != (REORDER & operations))
-    {
-      // Free the allocated memory used to store the conversion table in the bidirectional line info run.
-      for(Vector<BidirectionalLineInfoRun>::Iterator it    = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(),
-                                                     endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End();
-          it != endIt;
-          ++it)
-      {
-        BidirectionalLineInfoRun& bidiLineInfo = *it;
-
-        free(bidiLineInfo.visualToLogicalMap);
-        bidiLineInfo.visualToLogicalMap = NULL;
-      }
-      mModel->mLogicalModel->mBidirectionalLineInfo.Clear();
-    }
-  }
-
-  if(NO_OPERATION != (SHAPE_TEXT & operations))
-  {
-    mModel->mVisualModel->mGlyphs.Clear();
-    mModel->mVisualModel->mGlyphsToCharacters.Clear();
-    mModel->mVisualModel->mCharactersToGlyph.Clear();
-    mModel->mVisualModel->mCharactersPerGlyph.Clear();
-    mModel->mVisualModel->mGlyphsPerCharacter.Clear();
-    mModel->mVisualModel->mGlyphPositions.Clear();
-  }
-
-  if(NO_OPERATION != (LAYOUT & operations))
-  {
-    mModel->mVisualModel->mLines.Clear();
-  }
-
-  if(NO_OPERATION != (COLOR & operations))
-  {
-    mModel->mVisualModel->mColorIndices.Clear();
-    mModel->mVisualModel->mBackgroundColorIndices.Clear();
-  }
-}
-
-void Controller::Impl::ClearCharacterModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
-{
-  const CharacterIndex endIndexPlusOne = endIndex + 1u;
-
-  if(NO_OPERATION != (GET_LINE_BREAKS & operations))
-  {
-    // Clear the line break info.
-    LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
-
-    mModel->mLogicalModel->mLineBreakInfo.Erase(lineBreakInfoBuffer + startIndex,
-                                                lineBreakInfoBuffer + endIndexPlusOne);
-
-    // Clear the paragraphs.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mParagraphInfo);
-  }
-
-  if(NO_OPERATION != (GET_SCRIPTS & operations))
-  {
-    // Clear the scripts.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mScriptRuns);
-  }
-
-  if(NO_OPERATION != (VALIDATE_FONTS & operations))
-  {
-    // Clear the fonts.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mFontRuns);
-  }
-
-  if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count())
-  {
-    if(NO_OPERATION != (BIDI_INFO & operations))
-    {
-      // Clear the bidirectional paragraph info.
-      ClearCharacterRuns(startIndex,
-                         endIndex,
-                         mModel->mLogicalModel->mBidirectionalParagraphInfo);
-
-      // Clear the character's directions.
-      CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin();
-
-      mModel->mLogicalModel->mCharacterDirections.Erase(characterDirectionsBuffer + startIndex,
-                                                        characterDirectionsBuffer + endIndexPlusOne);
-    }
-
-    if(NO_OPERATION != (REORDER & operations))
-    {
-      uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count();
-      uint32_t endRemoveIndex   = startRemoveIndex;
-      ClearCharacterRuns(startIndex,
-                         endIndex,
-                         mModel->mLogicalModel->mBidirectionalLineInfo,
-                         startRemoveIndex,
-                         endRemoveIndex);
-
-      BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin();
-
-      // Free the allocated memory used to store the conversion table in the bidirectional line info run.
-      for(Vector<BidirectionalLineInfoRun>::Iterator it    = bidirectionalLineInfoBuffer + startRemoveIndex,
-                                                     endIt = bidirectionalLineInfoBuffer + endRemoveIndex;
-          it != endIt;
-          ++it)
-      {
-        BidirectionalLineInfoRun& bidiLineInfo = *it;
-
-        free(bidiLineInfo.visualToLogicalMap);
-        bidiLineInfo.visualToLogicalMap = NULL;
-      }
-
-      mModel->mLogicalModel->mBidirectionalLineInfo.Erase(bidirectionalLineInfoBuffer + startRemoveIndex,
-                                                          bidirectionalLineInfoBuffer + endRemoveIndex);
-    }
-  }
-}
-
-void Controller::Impl::ClearGlyphModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
-{
-  const CharacterIndex endIndexPlusOne           = endIndex + 1u;
-  const Length         numberOfCharactersRemoved = endIndexPlusOne - startIndex;
-
-  // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers.
-  GlyphIndex* charactersToGlyphBuffer  = mModel->mVisualModel->mCharactersToGlyph.Begin();
-  Length*     glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin();
-
-  const GlyphIndex endGlyphIndexPlusOne  = *(charactersToGlyphBuffer + endIndex) + *(glyphsPerCharacterBuffer + endIndex);
-  const Length     numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex;
-
-  if(NO_OPERATION != (SHAPE_TEXT & operations))
-  {
-    // Update the character to glyph indices.
-    for(Vector<GlyphIndex>::Iterator it    = charactersToGlyphBuffer + endIndexPlusOne,
-                                     endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count();
-        it != endIt;
-        ++it)
-    {
-      CharacterIndex& index = *it;
-      index -= numberOfGlyphsRemoved;
-    }
-
-    // Clear the character to glyph conversion table.
-    mModel->mVisualModel->mCharactersToGlyph.Erase(charactersToGlyphBuffer + startIndex,
-                                                   charactersToGlyphBuffer + endIndexPlusOne);
-
-    // Clear the glyphs per character table.
-    mModel->mVisualModel->mGlyphsPerCharacter.Erase(glyphsPerCharacterBuffer + startIndex,
-                                                    glyphsPerCharacterBuffer + endIndexPlusOne);
-
-    // Clear the glyphs buffer.
-    GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
-    mModel->mVisualModel->mGlyphs.Erase(glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                        glyphsBuffer + endGlyphIndexPlusOne);
-
-    CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin();
-
-    // Update the glyph to character indices.
-    for(Vector<CharacterIndex>::Iterator it    = glyphsToCharactersBuffer + endGlyphIndexPlusOne,
-                                         endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count();
-        it != endIt;
-        ++it)
-    {
-      CharacterIndex& index = *it;
-      index -= numberOfCharactersRemoved;
-    }
-
-    // Clear the glyphs to characters buffer.
-    mModel->mVisualModel->mGlyphsToCharacters.Erase(glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                                    glyphsToCharactersBuffer + endGlyphIndexPlusOne);
-
-    // Clear the characters per glyph buffer.
-    Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
-    mModel->mVisualModel->mCharactersPerGlyph.Erase(charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                                    charactersPerGlyphBuffer + endGlyphIndexPlusOne);
-
-    // Should pass if mGlyphPositions has already been cleared in Controller::Relayouter::Relayout
-    if(0u != mModel->mVisualModel->mGlyphPositions.Count())
-    {
-      // Clear the positions buffer.
-      Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin();
-      mModel->mVisualModel->mGlyphPositions.Erase(positionsBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                                  positionsBuffer + endGlyphIndexPlusOne);
-    }
-  }
-
-  if(NO_OPERATION != (LAYOUT & operations))
-  {
-    // Clear the lines.
-    uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count();
-    uint32_t endRemoveIndex   = startRemoveIndex;
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mVisualModel->mLines,
-                       startRemoveIndex,
-                       endRemoveIndex);
-
-    // Will update the glyph runs.
-    startRemoveIndex = mModel->mVisualModel->mLines.Count();
-    endRemoveIndex   = startRemoveIndex;
-    ClearGlyphRuns(mTextUpdateInfo.mStartGlyphIndex,
-                   endGlyphIndexPlusOne - 1u,
-                   mModel->mVisualModel->mLines,
-                   startRemoveIndex,
-                   endRemoveIndex);
-
-    // Set the line index from where to insert the new laid-out lines.
-    mTextUpdateInfo.mStartLineIndex = startRemoveIndex;
-
-    LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin();
-    mModel->mVisualModel->mLines.Erase(linesBuffer + startRemoveIndex,
-                                       linesBuffer + endRemoveIndex);
-  }
-
-  if(NO_OPERATION != (COLOR & operations))
-  {
-    if(0u != mModel->mVisualModel->mColorIndices.Count())
-    {
-      ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin();
-      mModel->mVisualModel->mColorIndices.Erase(colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                                colorIndexBuffer + endGlyphIndexPlusOne);
-    }
-
-    if(0u != mModel->mVisualModel->mBackgroundColorIndices.Count())
-    {
-      ColorIndex* backgroundColorIndexBuffer = mModel->mVisualModel->mBackgroundColorIndices.Begin();
-      mModel->mVisualModel->mBackgroundColorIndices.Erase(backgroundColorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                                          backgroundColorIndexBuffer + endGlyphIndexPlusOne);
-    }
-  }
-}
-
 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
 {
-  if(mTextUpdateInfo.mClearAll ||
-     ((0u == startIndex) &&
-      (mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u)))
-  {
-    ClearFullModelData(operations);
-  }
-  else
-  {
-    // Clear the model data related with characters.
-    ClearCharacterModelData(startIndex, endIndex, operations);
-
-    // Clear the model data related with glyphs.
-    ClearGlyphModelData(startIndex, endIndex, operations);
-  }
-
-  // The estimated number of lines. Used to avoid reallocations when layouting.
-  mTextUpdateInfo.mEstimatedNumberOfLines = std::max(mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count());
-
-  mModel->mVisualModel->ClearCaches();
+  ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
 }
 
 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
@@ -664,6 +640,32 @@ float Controller::Impl::GetDefaultFontLineHeight()
   return (fontMetrics.ascender - fontMetrics.descender);
 }
 
+bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
+{
+  if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
+  {
+    mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
+    mRecalculateNaturalSize = true;
+
+    RelayoutForNewLineSize();
+    return true;
+  }
+  return false;
+}
+
+bool Controller::Impl::SetDefaultLineSize(float lineSize)
+{
+  if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
+  {
+    mLayoutEngine.SetDefaultLineSize(lineSize);
+    mRecalculateNaturalSize = true;
+
+    RelayoutForNewLineSize();
+    return true;
+  }
+  return false;
+}
+
 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
 {
   if(nullptr == mEventData)
@@ -783,6 +785,26 @@ void Controller::Impl::SetEditable(bool editable)
   if(mEventData)
   {
     mEventData->mEditingEnabled = editable;
+
+    if(mEventData->mDecorator)
+    {
+      mEventData->mDecorator->SetEditable(editable);
+    }
+  }
+}
+
+void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
+{
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
+
+  if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
+    mFontDefaults->mFontDescription.family = newDefaultFont;
+
+    ClearFontData();
+
+    RequestRelayout();
   }
 }
 
@@ -1003,222 +1025,7 @@ void Controller::Impl::SetPopupButtons()
 
 void Controller::Impl::ChangeState(EventData::State newState)
 {
-  if(nullptr == mEventData)
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
-
-  DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d  newstate:%d\n", mEventData->mState, newState);
-
-  if(mEventData->mState != newState)
-  {
-    mEventData->mPreviousState = mEventData->mState;
-    mEventData->mState         = newState;
-
-    switch(mEventData->mState)
-    {
-      case EventData::INACTIVE:
-      {
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
-        mEventData->mDecorator->StopCursorBlink();
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-        mEventData->mDecorator->SetPopupActive(false);
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::INTERRUPTED:
-      {
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-        mEventData->mDecorator->SetPopupActive(false);
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::SELECTING:
-      {
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
-        mEventData->mDecorator->StopCursorBlink();
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
-          mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
-        }
-        mEventData->mDecorator->SetHighlightActive(true);
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          SetPopupButtons();
-          mEventData->mDecorator->SetPopupActive(true);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::EDITING:
-      {
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
-        if(mEventData->mCursorBlinkEnabled)
-        {
-          mEventData->mDecorator->StartCursorBlink();
-        }
-        // Grab handle is not shown until a tap is received whilst EDITING
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          mEventData->mDecorator->SetPopupActive(false);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::EDITING_WITH_POPUP:
-      {
-        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
-
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
-        if(mEventData->mCursorBlinkEnabled)
-        {
-          mEventData->mDecorator->StartCursorBlink();
-        }
-        if(mEventData->mSelectionEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-          mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-          mEventData->mDecorator->SetHighlightActive(false);
-        }
-        else if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true);
-        }
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          SetPopupButtons();
-          mEventData->mDecorator->SetPopupActive(true);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::EDITING_WITH_GRAB_HANDLE:
-      {
-        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
-
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
-        if(mEventData->mCursorBlinkEnabled)
-        {
-          mEventData->mDecorator->StartCursorBlink();
-        }
-        // Grab handle is not shown until a tap is received whilst EDITING
-        if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true);
-        }
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          mEventData->mDecorator->SetPopupActive(false);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::SELECTION_HANDLE_PANNING:
-      {
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
-        mEventData->mDecorator->StopCursorBlink();
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
-          mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
-        }
-        mEventData->mDecorator->SetHighlightActive(true);
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          mEventData->mDecorator->SetPopupActive(false);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::GRAB_HANDLE_PANNING:
-      {
-        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
-
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
-        if(mEventData->mCursorBlinkEnabled)
-        {
-          mEventData->mDecorator->StartCursorBlink();
-        }
-        if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true);
-        }
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          mEventData->mDecorator->SetPopupActive(false);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::EDITING_WITH_PASTE_POPUP:
-      {
-        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
-
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
-        if(mEventData->mCursorBlinkEnabled)
-        {
-          mEventData->mDecorator->StartCursorBlink();
-        }
-
-        if(mEventData->mGrabHandleEnabled)
-        {
-          mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true);
-        }
-        mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-        mEventData->mDecorator->SetHighlightActive(false);
-
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          SetPopupButtons();
-          mEventData->mDecorator->SetPopupActive(true);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-      case EventData::TEXT_PANNING:
-      {
-        mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
-        mEventData->mDecorator->StopCursorBlink();
-        mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false);
-        if(mEventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
-           mEventData->mDecorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
-        {
-          mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
-          mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
-          mEventData->mDecorator->SetHighlightActive(true);
-        }
-
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          mEventData->mDecorator->SetPopupActive(false);
-        }
-
-        mEventData->mDecoratorUpdated = true;
-        break;
-      }
-    }
-  }
+  ChangeTextControllerState(*this, newState);
 }
 
 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
@@ -1732,6 +1539,86 @@ Actor Controller::Impl::CreateBackgroundActor()
   return actor;
 }
 
+void Controller::Impl::RelayoutForNewLineSize()
+{
+  // relayout all characters
+  mTextUpdateInfo.mCharacterIndex             = 0;
+  mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
+  mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
+  mOperationsPending                          = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
+
+  //remove selection
+  if(mEventData && mEventData->mState == EventData::SELECTING)
+  {
+    ChangeState(EventData::EDITING);
+  }
+
+  RequestRelayout();
+}
+
+bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
+{
+  return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
+}
+
+void Controller::Impl::ProcessInputStyleChangedSignals()
+{
+  if(mEventData)
+  {
+    if(mEditableControlInterface)
+    {
+      // Emit the input style changed signal for each mask
+      std::for_each(mEventData->mInputStyleChangedQueue.begin(),
+                    mEventData->mInputStyleChangedQueue.end(),
+                    [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); } );
+    }
+
+    mEventData->mInputStyleChangedQueue.Clear();
+  }
+}
+
+void Controller::Impl::ScrollBy(Vector2 scroll)
+{
+  if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
+  {
+    const Vector2& layoutSize    = mModel->mVisualModel->GetLayoutSize();
+    const Vector2  currentScroll = mModel->mScrollPosition;
+
+    scroll.x = -scroll.x;
+    scroll.y = -scroll.y;
+
+    if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
+    {
+      mModel->mScrollPosition.x += scroll.x;
+      ClampHorizontalScroll(layoutSize);
+    }
+
+    if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
+    {
+      mModel->mScrollPosition.y += scroll.y;
+      ClampVerticalScroll(layoutSize);
+    }
+
+    if(mModel->mScrollPosition != currentScroll)
+    {
+      mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
+      RequestRelayout();
+    }
+  }
+}
+
+float Controller::Impl::GetHorizontalScrollPosition()
+{
+  // Scroll values are negative internally so we convert them to positive numbers
+  return mEventData ? -mModel->mScrollPosition.x : 0.0f;
+}
+
+float Controller::Impl::GetVerticalScrollPosition()
+{
+  // Scroll values are negative internally so we convert them to positive numbers
+  return mEventData ? -mModel->mScrollPosition.y : 0.0f;
+}
+
 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
 {
   //Underlined character runs for markup-processor
@@ -1758,4 +1645,197 @@ void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearP
   }
 }
 
+void Controller::Impl::SetAutoScrollEnabled(bool enable)
+{
+  if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
+  {
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
+                                                     LAYOUT |
+                                                     ALIGN |
+                                                     UPDATE_LAYOUT_SIZE |
+                                                     REORDER);
+
+    if(enable)
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
+      mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
+    }
+    else
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
+    }
+
+    mIsAutoScrollEnabled = enable;
+    RequestRelayout();
+  }
+  else
+  {
+    DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
+    mIsAutoScrollEnabled = false;
+  }
+}
+
+void Controller::Impl::SetEnableCursorBlink(bool enable)
+{
+  DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
+
+  if(mEventData)
+  {
+    mEventData->mCursorBlinkEnabled = enable;
+
+    if(!enable && mEventData->mDecorator)
+    {
+      mEventData->mDecorator->StopCursorBlink();
+    }
+  }
+}
+
+void Controller::Impl::SetMultiLineEnabled(bool enable)
+{
+  const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
+
+  if(layout != mLayoutEngine.GetLayout())
+  {
+    // Set the layout type.
+    mLayoutEngine.SetLayout(layout);
+
+    // Set the flags to redo the layout operations
+    const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
+                                                                        UPDATE_LAYOUT_SIZE |
+                                                                        ALIGN |
+                                                                        REORDER);
+
+    mTextUpdateInfo.mFullRelayoutNeeded = true;
+    mOperationsPending                  = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
+
+    // Need to recalculate natural size
+    mRecalculateNaturalSize = true;
+
+    RequestRelayout();
+  }
+}
+
+void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
+{
+  if(alignment != mModel->mHorizontalAlignment)
+  {
+    // Set the alignment.
+    mModel->mHorizontalAlignment = alignment;
+
+    // Set the flag to redo the alignment operation.
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
+
+    if(mEventData)
+    {
+      mEventData->mUpdateAlignment = true;
+
+      // Update the cursor if it's in editing mode
+      if(EventData::IsEditingState(mEventData->mState))
+      {
+        ChangeState(EventData::EDITING);
+        mEventData->mUpdateCursorPosition = true;
+      }
+    }
+
+    RequestRelayout();
+  }
+}
+
+void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
+{
+  if(alignment != mModel->mVerticalAlignment)
+  {
+    // Set the alignment.
+    mModel->mVerticalAlignment = alignment;
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
+    RequestRelayout();
+  }
+}
+
+void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
+{
+  if(lineWrapMode != mModel->mLineWrapMode)
+  {
+    // Update Text layout for applying wrap mode
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
+                                                     ALIGN |
+                                                     LAYOUT |
+                                                     UPDATE_LAYOUT_SIZE |
+                                                     REORDER);
+
+    if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
+       (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
+    {
+      mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
+    }
+
+    // Set the text wrap mode.
+    mModel->mLineWrapMode = lineWrapMode;
+
+    mTextUpdateInfo.mCharacterIndex             = 0u;
+    mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
+    mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
+
+    // Request relayout
+    RequestRelayout();
+  }
+}
+
+void Controller::Impl::SetDefaultColor(const Vector4& color)
+{
+  mTextColor = color;
+
+  if(!IsShowingPlaceholderText())
+  {
+    mModel->mVisualModel->SetTextColor(color);
+    mModel->mLogicalModel->mColorRuns.Clear();
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
+    RequestRelayout();
+  }
+}
+
+void Controller::Impl::ClearFontData()
+{
+  if(mFontDefaults)
+  {
+    mFontDefaults->mFontId = 0u; // Remove old font ID
+  }
+
+  // Set flags to update the model.
+  mTextUpdateInfo.mCharacterIndex             = 0u;
+  mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
+  mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
+
+  mTextUpdateInfo.mClearAll           = true;
+  mTextUpdateInfo.mFullRelayoutNeeded = true;
+  mRecalculateNaturalSize             = true;
+
+  mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
+                                                   VALIDATE_FONTS |
+                                                   SHAPE_TEXT |
+                                                   BIDI_INFO |
+                                                   GET_GLYPH_METRICS |
+                                                   LAYOUT |
+                                                   UPDATE_LAYOUT_SIZE |
+                                                   REORDER |
+                                                   ALIGN);
+}
+
+void Controller::Impl::ClearStyleData()
+{
+  mModel->mLogicalModel->mColorRuns.Clear();
+  mModel->mLogicalModel->ClearFontDescriptionRuns();
+}
+
+
+void Controller::Impl::ResetScrollPosition()
+{
+  if(mEventData)
+  {
+    // Reset the scroll position.
+    mModel->mScrollPosition                = Vector2::ZERO;
+    mEventData->mScrollAfterUpdatePosition = true;
+  }
+}
+
 } // namespace Dali::Toolkit::Text
index 51e0438..814f3a8 100644 (file)
@@ -545,6 +545,11 @@ struct Controller::Impl
   }
 
   /**
+   * @copydoc Controller::GetLayoutDirection()
+   */
+  Dali::LayoutDirection::Type GetLayoutDirection(Dali::Actor& actor) const;
+
+  /**
    * @brief Calculates the start character index of the first paragraph to be updated and
    * the end character index of the last paragraph to be updated.
    *
@@ -553,36 +558,6 @@ struct Controller::Impl
   void CalculateTextUpdateIndices(Length& numberOfCharacters);
 
   /**
-   * @brief Helper to clear completely the parts of the model specified by the given @p operations.
-   *
-   * @note It never clears the text stored in utf32.
-   */
-  void ClearFullModelData(OperationsMask operations);
-
-  /**
-   * @brief Helper to clear completely the parts of the model related with the characters specified by the given @p operations.
-   *
-   * @note It never clears the text stored in utf32.
-   *
-   * @param[in] startIndex Index to the first character to be cleared.
-   * @param[in] endIndex Index to the last character to be cleared.
-   * @param[in] operations The operations required.
-   */
-  void ClearCharacterModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations);
-
-  /**
-   * @brief Helper to clear completely the parts of the model related with the glyphs specified by the given @p operations.
-   *
-   * @note It never clears the text stored in utf32.
-   * @note Character indices are transformed to glyph indices.
-   *
-   * @param[in] startIndex Index to the first character to be cleared.
-   * @param[in] endIndex Index to the last character to be cleared.
-   * @param[in] operations The operations required.
-   */
-  void ClearGlyphModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations);
-
-  /**
    * @brief Helper to clear the parts of the model specified by the given @p operations and from @p startIndex to @p endIndex.
    *
    * @note It never clears the text stored in utf32.
@@ -620,6 +595,16 @@ struct Controller::Impl
   float GetDefaultFontLineHeight();
 
   /**
+   * @copydoc Controller::SetDefaultLineSpacing
+   */
+  bool SetDefaultLineSpacing(float lineSpacing);
+
+  /**
+   * @copydoc Controller::SetDefaultLineSize
+   */
+  bool SetDefaultLineSize(float lineSize);
+
+  /**
    * @copydoc Text::Controller::GetPrimaryCursorPosition()
    */
   CharacterIndex GetPrimaryCursorPosition() const;
@@ -650,6 +635,11 @@ struct Controller::Impl
   void SetEditable(bool editable);
 
   /**
+   * @copydoc Controller::UpdateAfterFontChange
+   */
+  void UpdateAfterFontChange(const std::string& newDefaultFont);
+
+  /**
    * @brief Retrieves the selected text. It removes the text if the @p deleteAfterRetrieval parameter is @e true.
    *
    * @param[out] selectedText The selected text encoded in utf8.
@@ -771,6 +761,86 @@ struct Controller::Impl
    */
   Actor CreateBackgroundActor();
 
+  /**
+   * @brief fill needed relayout parameters when line size is changed & request relayout.
+   */
+  void RelayoutForNewLineSize();
+
+  /**
+   * @copydoc Controller::IsInputStyleChangedSignalsQueueEmpty
+   */
+  bool IsInputStyleChangedSignalsQueueEmpty();
+
+  /**
+   * @copydoc Controller::ProcessInputStyleChangedSignals
+   */
+  void ProcessInputStyleChangedSignals();
+
+  /**
+   * @copydoc Controller::ScrollBy()
+   */
+  void ScrollBy(Vector2 scroll);
+
+  /**
+   * @copydoc Controller::GetHorizontalScrollPosition()
+   */
+  float GetHorizontalScrollPosition();
+
+  /**
+   * @copydoc Controller::GetVerticalScrollPosition()
+   */
+  float GetVerticalScrollPosition();
+
+  /**
+   * @copydoc Controller::SetAutoScrollEnabled()
+   */
+  void SetAutoScrollEnabled(bool enable);
+
+  /**
+   * @copydoc Controller::SetEnableCursorBlink()
+   */
+  void SetEnableCursorBlink(bool enable);
+
+  /**
+   * @copydoc Controller::SetMultiLineEnabled()
+   */
+  void SetMultiLineEnabled(bool enable);
+
+  /**
+   * @copydoc Controller::SetHorizontalAlignment()
+   */
+  void SetHorizontalAlignment(HorizontalAlignment::Type alignment);
+
+  /**
+   * @copydoc Controller::SetVerticalAlignment()
+   */
+  void SetVerticalAlignment(VerticalAlignment::Type alignment);
+
+  /**
+   * @copydoc Controller::SetLineWrapMode()
+   */
+  void SetLineWrapMode(Text::LineWrap::Mode textWarpMode);
+
+  /**
+   * @copydoc Controller::SetDefaultColor()
+   */
+  void SetDefaultColor(const Vector4& color);
+
+  /**
+   * @brief Helper to clear font-specific data (only).
+   */
+  void ClearFontData();
+
+  /**
+   * @brief Helper to clear text's style data.
+   */
+  void ClearStyleData();
+
+  /**
+   * @brief Used to reset the scroll position after setting a new text.
+   */
+  void ResetScrollPosition();
+
 public:
   /**
    * @brief Gets implementation from the controller handle.
diff --git a/dali-toolkit/internal/text/text-controller-input-properties.cpp b/dali-toolkit/internal/text/text-controller-input-properties.cpp
new file mode 100644 (file)
index 0000000..99ef15a
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/text/text-controller-input-properties.h>
+
+// EXTERNAL INCLUDES
+//#include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
+//#include <dali/devel-api/adaptor-framework/window-devel.h>
+//#include <dali/integration-api/debug.h>
+#include <memory.h>
+#include <cmath>
+#include <limits>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/text/text-enumerations-devel.h>
+#include <dali-toolkit/internal/text/text-controller-event-handler.h>
+#include <dali-toolkit/internal/text/text-controller-impl.h>
+#include <dali-toolkit/internal/text/text-controller-input-font-handler.h>
+#include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
+#include <dali-toolkit/internal/text/text-controller-relayouter.h>
+#include <dali-toolkit/internal/text/text-controller-text-updater.h>
+#include <dali-toolkit/internal/text/text-editable-control-interface.h>
+
+namespace
+{
+const std::string EMPTY_STRING("");
+}
+
+namespace Dali::Toolkit::Text
+{
+
+void Controller::InputProperties::SetInputColor(Controller& controller, const Vector4& color)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.textColor      = color;
+    controller.mImpl->mEventData->mInputStyle.isDefaultColor = false;
+
+    if(EventData::SELECTING == controller.mImpl->mEventData->mState || EventData::EDITING == controller.mImpl->mEventData->mState || EventData::INACTIVE == controller.mImpl->mEventData->mState)
+    {
+      if(EventData::SELECTING == controller.mImpl->mEventData->mState)
+      {
+        const bool handlesCrossed = controller.mImpl->mEventData->mLeftSelectionPosition > controller.mImpl->mEventData->mRightSelectionPosition;
+
+        // Get start and end position of selection
+        const CharacterIndex startOfSelectedText  = handlesCrossed ? controller.mImpl->mEventData->mRightSelectionPosition : controller.mImpl->mEventData->mLeftSelectionPosition;
+        const Length         lengthOfSelectedText = (handlesCrossed ? controller.mImpl->mEventData->mLeftSelectionPosition : controller.mImpl->mEventData->mRightSelectionPosition) - startOfSelectedText;
+
+        // Add the color run.
+        const VectorBase::SizeType numberOfRuns = controller.mImpl->mModel->mLogicalModel->mColorRuns.Count();
+        controller.mImpl->mModel->mLogicalModel->mColorRuns.Resize(numberOfRuns + 1u);
+
+        ColorRun& colorRun                       = *(controller.mImpl->mModel->mLogicalModel->mColorRuns.Begin() + numberOfRuns);
+        colorRun.color                           = color;
+        colorRun.characterRun.characterIndex     = startOfSelectedText;
+        colorRun.characterRun.numberOfCharacters = lengthOfSelectedText;
+
+        controller.mImpl->mTextUpdateInfo.mCharacterIndex             = startOfSelectedText;
+        controller.mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
+        controller.mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = lengthOfSelectedText;
+      }
+      else
+      {
+        controller.mImpl->mTextUpdateInfo.mCharacterIndex             = 0;
+        controller.mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = controller.mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
+        controller.mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = controller.mImpl->mModel->mLogicalModel->mText.Count();
+      }
+
+      // Request to relayout.
+      controller.mImpl->mOperationsPending = static_cast<OperationsMask>(controller.mImpl->mOperationsPending | COLOR);
+      controller.mImpl->RequestRelayout();
+    }
+  }
+}
+
+const Vector4& Controller::InputProperties::GetInputColor(const Controller& controller)
+{
+  // Return event text input color if we have it, otherwise just return the default text's color
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.textColor : controller.mImpl->mTextColor;
+}
+
+void Controller::InputProperties::SetInputLineSpacing(Controller& controller, float lineSpacing)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.lineSpacing          = lineSpacing;
+    controller.mImpl->mEventData->mInputStyle.isLineSpacingDefined = true;
+  }
+}
+
+float Controller::InputProperties::GetInputLineSpacing(const Controller& controller)
+{
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.lineSpacing : 0.0f;
+}
+
+void Controller::InputProperties::SetInputShadowProperties(Controller& controller, const std::string& shadowProperties)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.shadowProperties = shadowProperties;
+  }
+}
+
+const std::string& Controller::InputProperties::GetInputShadowProperties(const Controller& controller)
+{
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.shadowProperties : EMPTY_STRING;
+}
+
+void Controller::InputProperties::SetInputUnderlineProperties(Controller& controller, const std::string& underlineProperties)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.underlineProperties = underlineProperties;
+  }
+}
+
+const std::string& Controller::InputProperties::GetInputUnderlineProperties(const Controller& controller)
+{
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.underlineProperties : EMPTY_STRING;
+}
+
+void Controller::InputProperties::SetInputEmbossProperties(Controller& controller, const std::string& embossProperties)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.embossProperties = embossProperties;
+  }
+}
+
+const std::string& Controller::InputProperties::GetInputEmbossProperties(const Controller& controller)
+{
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.embossProperties : controller.GetDefaultEmbossProperties();
+}
+
+void Controller::InputProperties::SetInputOutlineProperties(Controller& controller, const std::string& outlineProperties)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mInputStyle.outlineProperties = outlineProperties;
+  }
+}
+
+const std::string& Controller::InputProperties::GetInputOutlineProperties(const Controller& controller)
+{
+  return controller.mImpl->mEventData ? controller.mImpl->mEventData->mInputStyle.outlineProperties : controller.GetDefaultOutlineProperties();
+}
+
+void Controller::InputProperties::SetInputModePassword(Controller& controller, bool passwordInput)
+{
+  if(controller.mImpl->mEventData)
+  {
+    controller.mImpl->mEventData->mPasswordInput = passwordInput;
+  }
+}
+
+bool Controller::InputProperties::IsInputModePassword(Controller& controller)
+{
+  return controller.mImpl->mEventData && controller.mImpl->mEventData->mPasswordInput;
+}
+
+} // namespace Dali::Toolkit::Text
diff --git a/dali-toolkit/internal/text/text-controller-input-properties.h b/dali-toolkit/internal/text/text-controller-input-properties.h
new file mode 100644 (file)
index 0000000..d453b5f
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef DALI_TOOLKIT_TEXT_CONTROLLER_INPUT_PROPERTIES_H
+#define DALI_TOOLKIT_TEXT_CONTROLLER_INPUT_PROPERTIES_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/input-method-context.h>
+#include <dali/public-api/events/gesture.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-controller.h>
+//#include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
+//#include <dali-toolkit/devel-api/controls/text-controls/text-selection-popup-callback-interface.h>
+//#include <dali-toolkit/devel-api/text/text-enumerations-devel.h>
+//#include <dali-toolkit/internal/text/decorator/text-decorator.h>
+//#include <dali-toolkit/internal/text/hidden-text.h>
+//#include <dali-toolkit/internal/text/input-filter.h>
+//#include <dali-toolkit/internal/text/layouts/layout-engine.h>
+//#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
+//#include <dali-toolkit/internal/text/text-model-interface.h>
+//#include <dali-toolkit/internal/text/text-selectable-control-interface.h>
+//#include <dali-toolkit/public-api/text/text-enumerations.h>
+
+namespace Dali::Toolkit::Text
+{
+struct Controller::InputProperties
+{
+  static void SetInputColor(Controller& controller, const Vector4& color);
+
+  static const Vector4& GetInputColor(const Controller& controller);
+
+  static void SetInputLineSpacing(Controller& controller, float lineSpacing);
+
+  static float GetInputLineSpacing(const Controller& controller);
+
+  static void SetInputShadowProperties(Controller& controller, const std::string& shadowProperties);
+
+  static const std::string& GetInputShadowProperties(const Controller& controller);
+
+  static void SetInputUnderlineProperties(Controller& controller, const std::string& underlineProperties);
+
+  static const std::string& GetInputUnderlineProperties(const Controller& controller);
+
+  static void SetInputEmbossProperties(Controller& controller, const std::string& embossProperties);
+
+  static const std::string& GetInputEmbossProperties(const Controller& controller);
+
+  static void SetInputOutlineProperties(Controller& controller, const std::string& outlineProperties);
+
+  static const std::string& GetInputOutlineProperties(const Controller& controller);
+
+  static void SetInputModePassword(Controller& controller, bool passwordInput);
+
+  static bool IsInputModePassword(Controller& controller);
+};
+
+
+} // namespace Dali::Toolkit::Text
+
+#endif // DALI_TOOLKIT_TEXT_CONTROLLER_INPUT_PROPERTIES_H
index ed0ddc6..c38f92e 100644 (file)
@@ -170,7 +170,7 @@ bool Controller::Relayouter::CheckForTextFit(Controller& controller, float point
   TextUpdateInfo&   textUpdateInfo  = impl.mTextUpdateInfo;
   impl.mFontDefaults->mFitPointSize = pointSize;
   impl.mFontDefaults->sizeDefined   = true;
-  controller.ClearFontData();
+  impl.ClearFontData();
 
   // Operations that can be done only once until the text changes.
   const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
@@ -262,7 +262,7 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const
     }
     impl.mFontDefaults->mFitPointSize = pointSizeArray[bestSizeIndex];
     impl.mFontDefaults->sizeDefined   = true;
-    controller.ClearFontData();
+    impl.ClearFontData();
   }
 }
 
@@ -566,7 +566,7 @@ bool Controller::Relayouter::DoRelayout(Controller& controller, const Size& size
       // Reset the scroll position in inactive state
       if(elideTextEnabled && (impl.mEventData->mState == EventData::INACTIVE))
       {
-        controller.ResetScrollPosition();
+        impl.ResetScrollPosition();
       }
     }
 
index 75ea73c..8a19786 100644 (file)
@@ -55,7 +55,7 @@ void Controller::TextUpdater::SetText(Controller& controller, const std::string&
   ResetText(controller);
 
   // Remove the style.
-  controller.ClearStyleData();
+  impl.ClearStyleData();
 
   CharacterIndex lastCursorIndex = 0u;
 
@@ -150,7 +150,7 @@ void Controller::TextUpdater::SetText(Controller& controller, const std::string&
   controller.ResetCursorPosition(lastCursorIndex);
 
   // Scrolls the text to make the cursor visible.
-  controller.ResetScrollPosition();
+  impl.ResetScrollPosition();
 
   impl.RequestRelayout();
 
index 832f09a..4ec306b 100644 (file)
@@ -31,6 +31,7 @@
 #include <dali-toolkit/internal/text/text-controller-event-handler.h>
 #include <dali-toolkit/internal/text/text-controller-impl.h>
 #include <dali-toolkit/internal/text/text-controller-input-font-handler.h>
+#include <dali-toolkit/internal/text/text-controller-input-properties.h>
 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
 #include <dali-toolkit/internal/text/text-controller-relayouter.h>
 #include <dali-toolkit/internal/text/text-controller-text-updater.h>
@@ -137,7 +138,7 @@ void Controller::SetGlyphType(TextAbstraction::GlyphType glyphType)
   mImpl->mMetrics->SetGlyphType(glyphType);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -169,39 +170,12 @@ bool Controller::HasAnchors() const
 void Controller::SetAutoScrollEnabled(bool enable)
 {
   DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled[%s] SingleBox[%s]-> [%p]\n", (enable) ? "true" : "false", (mImpl->mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX) ? "true" : "false", this);
-
-  if(mImpl->mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
-  {
-    mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending |
-                                                            LAYOUT |
-                                                            ALIGN |
-                                                            UPDATE_LAYOUT_SIZE |
-                                                            REORDER);
-
-    if(enable)
-    {
-      DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
-      mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | UPDATE_DIRECTION);
-    }
-    else
-    {
-      DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
-    }
-
-    mImpl->mIsAutoScrollEnabled = enable;
-    mImpl->RequestRelayout();
-  }
-  else
-  {
-    DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
-    mImpl->mIsAutoScrollEnabled = false;
-  }
+  mImpl->SetAutoScrollEnabled(enable);
 }
 
 bool Controller::IsAutoScrollEnabled() const
 {
   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::IsAutoScrollEnabled[%s]\n", mImpl->mIsAutoScrollEnabled ? "true" : "false");
-
   return mImpl->mIsAutoScrollEnabled;
 }
 
@@ -271,17 +245,7 @@ int Controller::GetMaximumNumberOfCharacters()
 
 void Controller::SetEnableCursorBlink(bool enable)
 {
-  DALI_ASSERT_DEBUG(NULL != mImpl->mEventData && "TextInput disabled");
-
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mCursorBlinkEnabled = enable;
-
-    if(!enable && mImpl->mEventData->mDecorator)
-    {
-      mImpl->mEventData->mDecorator->StopCursorBlink();
-    }
-  }
+  mImpl->SetEnableCursorBlink(enable);
 }
 
 bool Controller::GetEnableCursorBlink() const
@@ -291,27 +255,7 @@ bool Controller::GetEnableCursorBlink() const
 
 void Controller::SetMultiLineEnabled(bool enable)
 {
-  const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
-
-  if(layout != mImpl->mLayoutEngine.GetLayout())
-  {
-    // Set the layout type.
-    mImpl->mLayoutEngine.SetLayout(layout);
-
-    // Set the flags to redo the layout operations
-    const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
-                                                                        UPDATE_LAYOUT_SIZE |
-                                                                        ALIGN |
-                                                                        REORDER);
-
-    mImpl->mTextUpdateInfo.mFullRelayoutNeeded = true;
-    mImpl->mOperationsPending                  = static_cast<OperationsMask>(mImpl->mOperationsPending | layoutOperations);
-
-    // Need to recalculate natural size
-    mImpl->mRecalculateNaturalSize = true;
-
-    mImpl->RequestRelayout();
-  }
+  mImpl->SetMultiLineEnabled(enable);
 }
 
 bool Controller::IsMultiLineEnabled() const
@@ -321,28 +265,7 @@ bool Controller::IsMultiLineEnabled() const
 
 void Controller::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
 {
-  if(alignment != mImpl->mModel->mHorizontalAlignment)
-  {
-    // Set the alignment.
-    mImpl->mModel->mHorizontalAlignment = alignment;
-
-    // Set the flag to redo the alignment operation.
-    mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | ALIGN);
-
-    if(mImpl->mEventData)
-    {
-      mImpl->mEventData->mUpdateAlignment = true;
-
-      // Update the cursor if it's in editing mode
-      if(EventData::IsEditingState(mImpl->mEventData->mState))
-      {
-        mImpl->ChangeState(EventData::EDITING);
-        mImpl->mEventData->mUpdateCursorPosition = true;
-      }
-    }
-
-    mImpl->RequestRelayout();
-  }
+  mImpl->SetHorizontalAlignment(alignment);
 }
 
 Text::HorizontalAlignment::Type Controller::GetHorizontalAlignment() const
@@ -352,13 +275,7 @@ Text::HorizontalAlignment::Type Controller::GetHorizontalAlignment() const
 
 void Controller::SetVerticalAlignment(VerticalAlignment::Type alignment)
 {
-  if(alignment != mImpl->mModel->mVerticalAlignment)
-  {
-    // Set the alignment.
-    mImpl->mModel->mVerticalAlignment = alignment;
-    mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | ALIGN);
-    mImpl->RequestRelayout();
-  }
+  mImpl->SetVerticalAlignment(alignment);
 }
 
 VerticalAlignment::Type Controller::GetVerticalAlignment() const
@@ -416,31 +333,7 @@ bool Controller::IsShowingRealText() const
 
 void Controller::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
 {
-  if(lineWrapMode != mImpl->mModel->mLineWrapMode)
-  {
-    // Update Text layout for applying wrap mode
-    mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending |
-                                                            ALIGN |
-                                                            LAYOUT |
-                                                            UPDATE_LAYOUT_SIZE |
-                                                            REORDER);
-
-    if((mImpl->mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
-       (mImpl->mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
-    {
-      mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | GET_LINE_BREAKS);
-    }
-
-    // Set the text wrap mode.
-    mImpl->mModel->mLineWrapMode = lineWrapMode;
-
-    mImpl->mTextUpdateInfo.mCharacterIndex             = 0u;
-    mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
-    mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = mImpl->mModel->mLogicalModel->mText.Count();
-
-    // Request relayout
-    mImpl->RequestRelayout();
-  }
+  mImpl->SetLineWrapMode(lineWrapMode);
 }
 
 Text::LineWrap::Mode Controller::GetLineWrapMode() const
@@ -604,17 +497,7 @@ void Controller::GetPlaceholderText(PlaceholderType type, std::string& text) con
 
 void Controller::UpdateAfterFontChange(const std::string& newDefaultFont)
 {
-  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
-
-  if(!mImpl->mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
-  {
-    DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
-    mImpl->mFontDefaults->mFontDescription.family = newDefaultFont;
-
-    ClearFontData();
-
-    mImpl->RequestRelayout();
-  }
+  mImpl->UpdateAfterFontChange(newDefaultFont);
 }
 
 void Controller::RetrieveSelection(std::string& selectedText) const
@@ -654,7 +537,7 @@ void Controller::SetDefaultFontFamily(const std::string& defaultFontFamily)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -685,7 +568,7 @@ void Controller::SetDefaultFontWeight(FontWeight weight)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -726,7 +609,7 @@ void Controller::SetDefaultFontWidth(FontWidth width)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -767,7 +650,7 @@ void Controller::SetDefaultFontSlant(FontSlant slant)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -805,7 +688,7 @@ void Controller::SetFontSizeScale(float scale)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -826,7 +709,7 @@ void Controller::SetDefaultFontSize(float fontSize, FontSizeType type)
   UpdateCursorPosition(mImpl->mEventData);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -852,15 +735,7 @@ float Controller::GetPlaceholderTextFontSize(FontSizeType type) const
 
 void Controller::SetDefaultColor(const Vector4& color)
 {
-  mImpl->mTextColor = color;
-
-  if(!mImpl->IsShowingPlaceholderText())
-  {
-    mImpl->mModel->mVisualModel->SetTextColor(color);
-    mImpl->mModel->mLogicalModel->mColorRuns.Clear();
-    mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | COLOR);
-    mImpl->RequestRelayout();
-  }
+  mImpl->SetDefaultColor(color);
 }
 
 const Vector4& Controller::GetDefaultColor() const
@@ -1013,34 +888,9 @@ const std::string& Controller::GetDefaultOutlineProperties() const
   return mImpl->mOutlineDefaults ? mImpl->mOutlineDefaults->properties : EMPTY_STRING;
 }
 
-void Controller::RelayoutForNewLineSize()
-{
-  // relayout all characters
-  mImpl->mTextUpdateInfo.mCharacterIndex             = 0;
-  mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
-  mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = mImpl->mModel->mLogicalModel->mText.Count();
-  mImpl->mOperationsPending                          = static_cast<OperationsMask>(mImpl->mOperationsPending | LAYOUT);
-
-  //remove selection
-  if(mImpl->mEventData && mImpl->mEventData->mState == EventData::SELECTING)
-  {
-    mImpl->ChangeState(EventData::EDITING);
-  }
-
-  mImpl->RequestRelayout();
-}
-
 bool Controller::SetDefaultLineSpacing(float lineSpacing)
 {
-  if(std::fabs(lineSpacing - mImpl->mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
-  {
-    mImpl->mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
-    mImpl->mRecalculateNaturalSize = true;
-
-    RelayoutForNewLineSize();
-    return true;
-  }
-  return false;
+  return mImpl->SetDefaultLineSpacing(lineSpacing);
 }
 
 float Controller::GetDefaultLineSpacing() const
@@ -1050,15 +900,7 @@ float Controller::GetDefaultLineSpacing() const
 
 bool Controller::SetDefaultLineSize(float lineSize)
 {
-  if(std::fabs(lineSize - mImpl->mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
-  {
-    mImpl->mLayoutEngine.SetDefaultLineSize(lineSize);
-    mImpl->mRecalculateNaturalSize = true;
-
-    RelayoutForNewLineSize();
-    return true;
-  }
-  return false;
+  return mImpl->SetDefaultLineSize(lineSize);
 }
 
 float Controller::GetDefaultLineSize() const
@@ -1068,52 +910,12 @@ float Controller::GetDefaultLineSize() const
 
 void Controller::SetInputColor(const Vector4& color)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.textColor      = color;
-    mImpl->mEventData->mInputStyle.isDefaultColor = false;
-
-    if(EventData::SELECTING == mImpl->mEventData->mState || EventData::EDITING == mImpl->mEventData->mState || EventData::INACTIVE == mImpl->mEventData->mState)
-    {
-      if(EventData::SELECTING == mImpl->mEventData->mState)
-      {
-        const bool handlesCrossed = mImpl->mEventData->mLeftSelectionPosition > mImpl->mEventData->mRightSelectionPosition;
-
-        // Get start and end position of selection
-        const CharacterIndex startOfSelectedText  = handlesCrossed ? mImpl->mEventData->mRightSelectionPosition : mImpl->mEventData->mLeftSelectionPosition;
-        const Length         lengthOfSelectedText = (handlesCrossed ? mImpl->mEventData->mLeftSelectionPosition : mImpl->mEventData->mRightSelectionPosition) - startOfSelectedText;
-
-        // Add the color run.
-        const VectorBase::SizeType numberOfRuns = mImpl->mModel->mLogicalModel->mColorRuns.Count();
-        mImpl->mModel->mLogicalModel->mColorRuns.Resize(numberOfRuns + 1u);
-
-        ColorRun& colorRun                       = *(mImpl->mModel->mLogicalModel->mColorRuns.Begin() + numberOfRuns);
-        colorRun.color                           = color;
-        colorRun.characterRun.characterIndex     = startOfSelectedText;
-        colorRun.characterRun.numberOfCharacters = lengthOfSelectedText;
-
-        mImpl->mTextUpdateInfo.mCharacterIndex             = startOfSelectedText;
-        mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
-        mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = lengthOfSelectedText;
-      }
-      else
-      {
-        mImpl->mTextUpdateInfo.mCharacterIndex             = 0;
-        mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
-        mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = mImpl->mModel->mLogicalModel->mText.Count();
-      }
-
-      // Request to relayout.
-      mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending | COLOR);
-      mImpl->RequestRelayout();
-    }
-  }
+  InputProperties::SetInputColor(*this, color);
 }
 
 const Vector4& Controller::GetInputColor() const
 {
-  // Return event text input color if we have it, otherwise just return the default text's color
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.textColor : mImpl->mTextColor;
+  return InputProperties::GetInputColor(*this);
 }
 
 void Controller::SetInputFontFamily(const std::string& fontFamily)
@@ -1183,81 +985,62 @@ float Controller::GetInputFontPointSize() const
 
 void Controller::SetInputLineSpacing(float lineSpacing)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.lineSpacing          = lineSpacing;
-    mImpl->mEventData->mInputStyle.isLineSpacingDefined = true;
-  }
+  InputProperties::SetInputLineSpacing(*this, lineSpacing);
 }
 
 float Controller::GetInputLineSpacing() const
 {
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.lineSpacing : 0.0f;
+  return InputProperties::GetInputLineSpacing(*this);
 }
 
 void Controller::SetInputShadowProperties(const std::string& shadowProperties)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.shadowProperties = shadowProperties;
-  }
+  InputProperties::SetInputShadowProperties(*this, shadowProperties);
 }
 
 const std::string& Controller::GetInputShadowProperties() const
 {
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.shadowProperties : EMPTY_STRING;
+  return InputProperties::GetInputShadowProperties(*this);
 }
 
 void Controller::SetInputUnderlineProperties(const std::string& underlineProperties)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.underlineProperties = underlineProperties;
-  }
+  InputProperties::SetInputUnderlineProperties(*this, underlineProperties);
 }
 
 const std::string& Controller::GetInputUnderlineProperties() const
 {
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.underlineProperties : EMPTY_STRING;
+  return InputProperties::GetInputUnderlineProperties(*this);
 }
 
 void Controller::SetInputEmbossProperties(const std::string& embossProperties)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.embossProperties = embossProperties;
-  }
+  InputProperties::SetInputEmbossProperties(*this, embossProperties);
 }
 
 const std::string& Controller::GetInputEmbossProperties() const
 {
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.embossProperties : GetDefaultEmbossProperties();
+  return InputProperties::GetInputEmbossProperties(*this);
 }
 
 void Controller::SetInputOutlineProperties(const std::string& outlineProperties)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mInputStyle.outlineProperties = outlineProperties;
-  }
+  InputProperties::SetInputOutlineProperties(*this, outlineProperties);
 }
 
 const std::string& Controller::GetInputOutlineProperties() const
 {
-  return mImpl->mEventData ? mImpl->mEventData->mInputStyle.outlineProperties : GetDefaultOutlineProperties();
+  return InputProperties::GetInputOutlineProperties(*this);
 }
 
 void Controller::SetInputModePassword(bool passwordInput)
 {
-  if(mImpl->mEventData)
-  {
-    mImpl->mEventData->mPasswordInput = passwordInput;
-  }
+  InputProperties::SetInputModePassword(*this, passwordInput);
 }
 
 bool Controller::IsInputModePassword()
 {
-  return mImpl->mEventData && mImpl->mEventData->mPasswordInput;
+  return InputProperties::IsInputModePassword(*this);
 }
 
 void Controller::SetNoTextDoubleTapAction(NoTextTap::Action action)
@@ -1506,23 +1289,12 @@ void Controller::RequestRelayout()
 
 bool Controller::IsInputStyleChangedSignalsQueueEmpty()
 {
-  return (NULL == mImpl->mEventData) || (0u == mImpl->mEventData->mInputStyleChangedQueue.Count());
+  return mImpl->IsInputStyleChangedSignalsQueueEmpty();
 }
 
 void Controller::ProcessInputStyleChangedSignals()
 {
-  if(mImpl->mEventData)
-  {
-    if(mImpl->mEditableControlInterface)
-    {
-      // Emit the input style changed signal for each mask
-      std::for_each(mImpl->mEventData->mInputStyleChangedQueue.begin(),
-                    mImpl->mEventData->mInputStyleChangedQueue.end(),
-                    [&](const auto mask) { mImpl->mEditableControlInterface->InputStyleChanged(mask); } );
-    }
-
-    mImpl->mEventData->mInputStyleChangedQueue.Clear();
-  }
+  mImpl->ProcessInputStyleChangedSignals();
 }
 
 void Controller::KeyboardFocusGainEvent()
@@ -1719,52 +1491,21 @@ bool Controller::IsEditable() const
 void Controller::SetEditable(bool editable)
 {
   mImpl->SetEditable(editable);
-  if(mImpl->mEventData && mImpl->mEventData->mDecorator)
-  {
-    mImpl->mEventData->mDecorator->SetEditable(editable);
-  }
 }
 
 void Controller::ScrollBy(Vector2 scroll)
 {
-  if(mImpl->mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
-  {
-    const Vector2& layoutSize    = mImpl->mModel->mVisualModel->GetLayoutSize();
-    const Vector2  currentScroll = mImpl->mModel->mScrollPosition;
-
-    scroll.x = -scroll.x;
-    scroll.y = -scroll.y;
-
-    if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
-    {
-      mImpl->mModel->mScrollPosition.x += scroll.x;
-      mImpl->ClampHorizontalScroll(layoutSize);
-    }
-
-    if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
-    {
-      mImpl->mModel->mScrollPosition.y += scroll.y;
-      mImpl->ClampVerticalScroll(layoutSize);
-    }
-
-    if(mImpl->mModel->mScrollPosition != currentScroll)
-    {
-      mImpl->mEventData->mDecorator->UpdatePositions(mImpl->mModel->mScrollPosition - currentScroll);
-      mImpl->RequestRelayout();
-    }
-  }
+  mImpl->ScrollBy(scroll);
 }
 
 float Controller::GetHorizontalScrollPosition()
 {
-  // Scroll values are negative internally so we convert them to positive numbers
-  return mImpl->mEventData ? -mImpl->mModel->mScrollPosition.x : 0.0f;
+  return mImpl->GetHorizontalScrollPosition();
 }
 
 float Controller::GetVerticalScrollPosition()
 {
-  // Scroll values are negative internally so we convert them to positive numbers
-  return mImpl->mEventData ? -mImpl->mModel->mScrollPosition.y : 0.0f;
+  return mImpl->GetVerticalScrollPosition();
 }
 
 void Controller::DecorationEvent(HandleType handleType, HandleState state, float x, float y)
@@ -1863,39 +1604,6 @@ void Controller::ShowPlaceholderText()
   PlaceholderHandler::ShowPlaceholderText(*this);
 }
 
-void Controller::ClearFontData()
-{
-  if(mImpl->mFontDefaults)
-  {
-    mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
-  }
-
-  // Set flags to update the model.
-  mImpl->mTextUpdateInfo.mCharacterIndex             = 0u;
-  mImpl->mTextUpdateInfo.mNumberOfCharactersToRemove = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
-  mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd    = mImpl->mModel->mLogicalModel->mText.Count();
-
-  mImpl->mTextUpdateInfo.mClearAll           = true;
-  mImpl->mTextUpdateInfo.mFullRelayoutNeeded = true;
-  mImpl->mRecalculateNaturalSize             = true;
-
-  mImpl->mOperationsPending = static_cast<OperationsMask>(mImpl->mOperationsPending |
-                                                          VALIDATE_FONTS |
-                                                          SHAPE_TEXT |
-                                                          BIDI_INFO |
-                                                          GET_GLYPH_METRICS |
-                                                          LAYOUT |
-                                                          UPDATE_LAYOUT_SIZE |
-                                                          REORDER |
-                                                          ALIGN);
-}
-
-void Controller::ClearStyleData()
-{
-  mImpl->mModel->mLogicalModel->mColorRuns.Clear();
-  mImpl->mModel->mLogicalModel->ClearFontDescriptionRuns();
-}
-
 void Controller::ResetCursorPosition(CharacterIndex cursorIndex)
 {
   // Reset the cursor position
@@ -1913,20 +1621,7 @@ void Controller::ResetCursorPosition(CharacterIndex cursorIndex)
 
 CharacterIndex Controller::GetCursorPosition()
 {
-  if(!mImpl->mEventData)
-    return 0;
-
-  return mImpl->mEventData->mPrimaryCursorPosition;
-}
-
-void Controller::ResetScrollPosition()
-{
-  if(mImpl->mEventData)
-  {
-    // Reset the scroll position.
-    mImpl->mModel->mScrollPosition                = Vector2::ZERO;
-    mImpl->mEventData->mScrollAfterUpdatePosition = true;
-  }
+  return mImpl->mEventData ? mImpl->mEventData->mPrimaryCursorPosition : 0;
 }
 
 void Controller::SetControlInterface(ControlInterface* controlInterface)
index 149e788..a5bc8b4 100644 (file)
@@ -1902,26 +1902,6 @@ private: // Helpers.
    */
   void ShowPlaceholderText();
 
-  /**
-   * @brief Helper to clear font-specific data (only).
-   */
-  void ClearFontData();
-
-  /**
-   * @brief Helper to clear text's style data.
-   */
-  void ClearStyleData();
-
-  /**
-   * @brief Used to reset the scroll position after setting a new text.
-   */
-  void ResetScrollPosition();
-
-  /**
-   * @brief fill needed relayout parameters when line size is changed & request relayout.
-   */
-  void RelayoutForNewLineSize();
-
 private: // Private contructors & copy operator.
   /**
    * @brief Private constructor.
@@ -1962,6 +1942,7 @@ public:
 private:
   struct EventHandler;
   struct InputFontHandler;
+  struct InputProperties;
   struct PlaceholderHandler;
   struct Relayouter;
   struct TextUpdater;
index d8d61d3..da7fb09 100644 (file)
@@ -833,10 +833,11 @@ Shader AnimatedImageVisual::GenerateShader() const
   Shader shader;
   shader = mImageVisualShaderFactory.GetShader(
     mFactoryCache,
-    TextureAtlas::DISABLED,
-    defaultWrapMode ? DefaultTextureWrapMode::APPLY : DefaultTextureWrapMode::DO_NOT_APPLY,
-    IsRoundedCornerRequired() ? RoundedCorner::ENABLED : RoundedCorner::DISABLED,
-    IsBorderlineRequired() ? Borderline::ENABLED : Borderline::DISABLED);
+    ImageVisualShaderFeature::FeatureBuilder()
+    .ApplyDefaultTextureWrapMode(defaultWrapMode)
+    .EnableRoundedCorner(IsRoundedCornerRequired())
+    .EnableBorderline(IsBorderlineRequired())
+  );
   return shader;
 }
 
index 26495bf..75b71f2 100644 (file)
@@ -626,10 +626,9 @@ Shader AnimatedVectorImageVisual::GenerateShader() const
   {
     shader = mImageVisualShaderFactory.GetShader(
       mFactoryCache,
-      TextureAtlas::DISABLED,
-      DefaultTextureWrapMode::APPLY,
-      IsRoundedCornerRequired() ? RoundedCorner::ENABLED : RoundedCorner::DISABLED,
-      IsBorderlineRequired() ? Borderline::ENABLED : Borderline::DISABLED
+      ImageVisualShaderFeature::FeatureBuilder()
+      .EnableRoundedCorner(IsRoundedCornerRequired())
+      .EnableBorderline(IsBorderlineRequired())
     );
   }
   return shader;
index 7f2cac7..f5753fa 100644 (file)
@@ -18,6 +18,7 @@
 #include <dali-toolkit/internal/visuals/image-visual-shader-factory.h>
 
 // EXTERNAL INCLUDES
+#include <dali/devel-api/rendering/texture-devel.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
@@ -40,9 +41,41 @@ static std::string gVertexShader;
 // global string variable to caching complate fragment shader (no atlas)
 static std::string gFragmentShaderNoAtlas;
 
+const int NATIVE_SHADER_TYPE_OFFSET = VisualFactoryCache::ShaderType::NATIVE_IMAGE_SHADER - VisualFactoryCache::ShaderType::IMAGE_SHADER;
+
 } // unnamed namespace
 
+namespace ImageVisualShaderFeature
+{
+FeatureBuilder& FeatureBuilder::EnableTextureAtlas(bool enableAtlas)
+{
+  mTextureAtlas = (enableAtlas ? TextureAtlas::ENABLED : TextureAtlas::DISABLED);
+  return *this;
+}
+FeatureBuilder& FeatureBuilder::ApplyDefaultTextureWrapMode(bool applyDefaultTextureWrapMode)
+{
+  mDefaultTextureWrapMode = (applyDefaultTextureWrapMode ? DefaultTextureWrapMode::APPLY : DefaultTextureWrapMode::DO_NOT_APPLY);
+  return *this;
+}
+FeatureBuilder& FeatureBuilder::EnableRoundedCorner(bool enableRoundedCorner)
+{
+  mRoundedCorner = (enableRoundedCorner ? RoundedCorner::ENABLED : RoundedCorner::DISABLED);
+  return *this;
+}
+FeatureBuilder& FeatureBuilder::EnableBorderline(bool enableBorderline)
+{
+  mBorderline = (enableBorderline ? Borderline::ENABLED : Borderline::DISABLED);
+  return *this;
+}
+FeatureBuilder& FeatureBuilder::SetTextureForFragmentShaderCheck(const Dali::Texture& texture)
+{
+  mTexture = texture;
+  return *this;
+}
+} // namespace ImageVisualShaderFeature
+
 ImageVisualShaderFactory::ImageVisualShaderFactory()
+: mFragmentShaderNeedChange(ImageVisualShaderFeature::ChangeFragmentShader::UNDECIDED)
 {
 }
 
@@ -50,13 +83,22 @@ ImageVisualShaderFactory::~ImageVisualShaderFactory()
 {
 }
 
-Shader ImageVisualShaderFactory::GetShader(VisualFactoryCache& factoryCache, TextureAtlas atlasing, DefaultTextureWrapMode defaultTextureWrapping, RoundedCorner roundedCorner, Borderline borderline)
+Shader ImageVisualShaderFactory::GetShader(VisualFactoryCache& factoryCache, const ImageVisualShaderFeature::FeatureBuilder& featureBuilder)
 {
   Shader shader;
   VisualFactoryCache::ShaderType shaderType = VisualFactoryCache::IMAGE_SHADER;
-  if(atlasing == TextureAtlas::ENABLED)
+
+  const auto& atlasing               = featureBuilder.mTextureAtlas;
+  const auto& defaultTextureWrapping = featureBuilder.mDefaultTextureWrapMode;
+  const auto& roundedCorner          = featureBuilder.mRoundedCorner;
+  const auto& borderline             = featureBuilder.mBorderline;
+  const auto& changeFragmentShader   = (featureBuilder.mTexture && DevelTexture::IsNative(featureBuilder.mTexture))
+                                       ? ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE
+                                       : ImageVisualShaderFeature::ChangeFragmentShader::DONT_CHANGE;
+
+  if(atlasing == ImageVisualShaderFeature::TextureAtlas::ENABLED)
   {
-    if(defaultTextureWrapping == DefaultTextureWrapMode::APPLY)
+    if(defaultTextureWrapping == ImageVisualShaderFeature::DefaultTextureWrapMode::APPLY)
     {
       shaderType = VisualFactoryCache::IMAGE_SHADER_ATLAS_DEFAULT_WRAP;
     }
@@ -67,9 +109,9 @@ Shader ImageVisualShaderFactory::GetShader(VisualFactoryCache& factoryCache, Tex
   }
   else
   {
-    if(roundedCorner == RoundedCorner::ENABLED)
+    if(roundedCorner == ImageVisualShaderFeature::RoundedCorner::ENABLED)
     {
-      if(borderline == Borderline::ENABLED)
+      if(borderline == ImageVisualShaderFeature::Borderline::ENABLED)
       {
         shaderType = VisualFactoryCache::IMAGE_SHADER_ROUNDED_BORDERLINE;
       }
@@ -80,21 +122,28 @@ Shader ImageVisualShaderFactory::GetShader(VisualFactoryCache& factoryCache, Tex
     }
     else
     {
-      if(borderline == Borderline::ENABLED)
+      if(borderline == ImageVisualShaderFeature::Borderline::ENABLED)
       {
         shaderType = VisualFactoryCache::IMAGE_SHADER_BORDERLINE;
       }
     }
   }
 
+  if(changeFragmentShader == ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE &&
+     (mFragmentShaderNeedChange == ImageVisualShaderFeature::ChangeFragmentShader::UNDECIDED ||
+      mFragmentShaderNeedChange == ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE))
+  {
+    shaderType = static_cast<VisualFactoryCache::ShaderType>(static_cast<int>(shaderType) + NATIVE_SHADER_TYPE_OFFSET);
+  }
+
   shader = factoryCache.GetShader(shaderType);
   if(!shader)
   {
     std::string vertexShaderPrefixList;
     std::string fragmentShaderPrefixList;
-    if(atlasing == TextureAtlas::ENABLED)
+    if(atlasing == ImageVisualShaderFeature::TextureAtlas::ENABLED)
     {
-      if(defaultTextureWrapping == DefaultTextureWrapMode::APPLY)
+      if(defaultTextureWrapping == ImageVisualShaderFeature::DefaultTextureWrapMode::APPLY)
       {
         fragmentShaderPrefixList += "#define ATLAS_DEFAULT_WARP 1\n";
       }
@@ -105,20 +154,70 @@ Shader ImageVisualShaderFactory::GetShader(VisualFactoryCache& factoryCache, Tex
     }
     else
     {
-      if(roundedCorner == RoundedCorner::ENABLED)
+      if(roundedCorner == ImageVisualShaderFeature::RoundedCorner::ENABLED)
       {
         vertexShaderPrefixList   += "#define IS_REQUIRED_ROUNDED_CORNER 1\n";
         fragmentShaderPrefixList += "#define IS_REQUIRED_ROUNDED_CORNER 1\n";
       }
-      if(borderline == Borderline::ENABLED)
+      if(borderline == ImageVisualShaderFeature::Borderline::ENABLED)
       {
         vertexShaderPrefixList   += "#define IS_REQUIRED_BORDERLINE 1\n";
         fragmentShaderPrefixList += "#define IS_REQUIRED_BORDERLINE 1\n";
       }
     }
 
-    shader = Shader::New(Dali::Shader::GetVertexShaderPrefix()   + vertexShaderPrefixList   + SHADER_IMAGE_VISUAL_SHADER_VERT.data(),
-                         Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderPrefixList + SHADER_IMAGE_VISUAL_SHADER_FRAG.data());
+    std::string vertexShader   = std::string(Dali::Shader::GetVertexShaderPrefix()   + vertexShaderPrefixList   + SHADER_IMAGE_VISUAL_SHADER_VERT.data());
+    std::string fragmentShader = std::string(Dali::Shader::GetFragmentShaderPrefix() + fragmentShaderPrefixList + SHADER_IMAGE_VISUAL_SHADER_FRAG.data());
+
+    if(changeFragmentShader == ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE)
+    {
+      if(DALI_UNLIKELY(mFragmentShaderNeedChange == ImageVisualShaderFeature::ChangeFragmentShader::UNDECIDED))
+      {
+        // NOTE : This routine will run exist one times.
+        //
+        // First, we will run ApplyNativeFragmentShader
+        //  - If fragment shader is modified, then current platform allow to change fragment shader.
+        //    We cache this result mFragmentShaderNeedChange = ChangeFragmentShader::NEED_CHANGE.
+        //  - If fragment shader is not modified, then current platform will always don't change fragment shader.
+        //    We cache this result mFragmentShaderNeedChange = ChangeFragmentShader::DONT_CHANGE.
+        //    And change current shaderType into normal image range.
+        //    After cached the result, shaderType never become NATIVE_IMAGE_SHADER anymore.
+        // Second, save shader result.
+
+        // Try to apply fragmentShader
+        bool modified = DevelTexture::ApplyNativeFragmentShader(featureBuilder.mTexture, fragmentShader);
+        if(modified)
+        {
+          // Now we know that fragment shader need to change.
+          mFragmentShaderNeedChange = ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE;
+        }
+        else
+        {
+          // Now we know that fragment shader even don't need to change.
+          // We can skip ApplyNativeFragmentShader routine after now.
+          mFragmentShaderNeedChange = ImageVisualShaderFeature::ChangeFragmentShader::DONT_CHANGE;
+
+          // Now we need normal shader type
+          // So decrease NATIVE_SHADER_TYPE_OFFSET.
+          shaderType = static_cast<VisualFactoryCache::ShaderType>(static_cast<int>(shaderType) - NATIVE_SHADER_TYPE_OFFSET);
+
+          // If we already compiled this type already, just use that cached shader.
+          // Else, just go forward.
+          shader = factoryCache.GetShader(shaderType);
+          if(shader)
+          {
+            return shader;
+          }
+        }
+      }
+      else if(mFragmentShaderNeedChange == ImageVisualShaderFeature::ChangeFragmentShader::NEED_CHANGE)
+      {
+        // Always need to apply fragmentShader
+        bool modified = DevelTexture::ApplyNativeFragmentShader(featureBuilder.mTexture, fragmentShader);
+        DALI_ASSERT_ALWAYS(modified && "NativeImageTexture need to change fragment shader. But DALI default image shader doesn't changed!");
+      }
+    }
+    shader = Shader::New(vertexShader, fragmentShader);
     shader.RegisterProperty(PIXEL_AREA_UNIFORM_NAME, FULL_TEXTURE_RECT);
     factoryCache.SaveShader(shaderType, shader);
   }
index 604b49b..1459614 100644 (file)
@@ -29,41 +29,101 @@ namespace Toolkit
 {
 namespace Internal
 {
+
+/**
+ * ImageVisualShaderFeature contains feature lists what image visual shader need to know.
+ */
+namespace ImageVisualShaderFeature
+{
+namespace TextureAtlas
+{
 /**
  * @brief Whether use texture with atlas, or not
  */
-enum class TextureAtlas
+enum Type
 {
-  DISABLED = 0, ///< Image visual use ATLAS
-  ENABLED       ///< Image visual doesn't use ATLAS
+  DISABLED = 0, ///< Image visual doesn't use ATLAS
+  ENABLED       ///< Image visual uses ATLAS
 };
+} // namespace TextureAtlas
 
+namespace DefaultTextureWrapMode
+{
 /**
  * @brief Whether apply to texture wraping in default, or not
  */
-enum class DefaultTextureWrapMode
+enum Type
 {
-  DO_NOT_APPLY = 0, ///< Image visual doesn't apply to wraping texture in default
-  APPLY             ///< Image visual apply to wraping texture in default
+  APPLY = 0,   ///< Image visual applies to wraping texture in default
+  DO_NOT_APPLY ///< Image visual doesn't apply to wraping texture in default
 };
+} // namespace DefaultTextureWrapMode
 
+namespace RoundedCorner
+{
 /**
  * @brief Whether use rounded corner, or not
  */
-enum class RoundedCorner
+enum Type
 {
   DISABLED = 0, ///< Image visual doesn't use rounded corner
-  ENABLED       ///< Image visual use rounded corner
+  ENABLED       ///< Image visual uses rounded corner
 };
+} // namespace RoundedCorner
 
+namespace Borderline
+{
 /**
  * @brief Whether use borderline, or not
  */
-enum class Borderline
+enum Type
 {
   DISABLED = 0, ///< Image visual doesn't use borderline
-  ENABLED       ///< Image visual use borderline
+  ENABLED       ///< Image visual uses borderline
 };
+} // namespace Borderline
+
+namespace ChangeFragmentShader
+{
+/**
+ * @brief Whether native image change the default fragment shader, or not
+ */
+enum Type
+{
+  DONT_CHANGE = 0, ///< Native image doesn't change default fragment shader.
+  NEED_CHANGE,     ///< Native image changes default fragment shader. We need another shader cache.
+  UNDECIDED,       ///< Undecided.
+};
+} // namespace ChangeFragmentShader
+
+/**
+ * @brief Collection of current image visual feature. Only use for ImageVisualShaderFactory::GetShader()
+ */
+struct FeatureBuilder
+{
+  FeatureBuilder()
+  : mTextureAtlas(TextureAtlas::DISABLED),
+    mDefaultTextureWrapMode(DefaultTextureWrapMode::APPLY),
+    mRoundedCorner(RoundedCorner::DISABLED),
+    mBorderline(Borderline::DISABLED),
+    mTexture()
+  {
+  }
+
+  FeatureBuilder& EnableTextureAtlas(bool enableTextureAtlas);
+  FeatureBuilder& ApplyDefaultTextureWrapMode(bool applyDefaultTextureWrapMode);
+  FeatureBuilder& EnableRoundedCorner(bool enableRoundedCorner);
+  FeatureBuilder& EnableBorderline(bool enableBorderline);
+  FeatureBuilder& SetTextureForFragmentShaderCheck(const Dali::Texture& texture);
+
+  TextureAtlas::Type           mTextureAtlas : 2;           ///< Whether use texture with atlas, or not. default as TextureAtlas::DISABLED
+  DefaultTextureWrapMode::Type mDefaultTextureWrapMode : 2; ///< Whether apply to texture wraping in default, or not. default as DefaultTextureWrapMode::APPLY
+  RoundedCorner::Type          mRoundedCorner : 2;          ///< Whether use rounded corner, or not. default as RoundedCorner::DISABLED
+  Borderline::Type             mBorderline : 2;             ///< Whether use borderline, or not. default as Borderline::DISABLED
+  Dali::Texture                mTexture;                    ///< Texture to check whether we need to change fragment shader or not
+};
+
+} // namespace ImageVisualShaderFactoryFeature
 
 /**
  * ImageVisualShaderFactory is an object that provides and shares shaders between image visuals
@@ -83,23 +143,21 @@ public:
   ~ImageVisualShaderFactory();
 
   /**
-   * Get the standard image rendering shader.
+   * @brief Get the standard image rendering shader.
    * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
-   * @param[in] atlasing Whether texture atlasing is applied.
-   * @param[in] defaultTextureWrapping Whether the default texture wrap mode is applied.
-   * @param[in] roundedCorner Whether the rounded corder is applied.
-   * @param[in] borderline Whether the borderline of visual is applied.
+   * @param[in] featureBuilder Collection of current image shader's features
+   * @return The standard image rendering shader with features.
    */
-  Shader GetShader(VisualFactoryCache& factoryCache, TextureAtlas atlasing, DefaultTextureWrapMode defaultTextureWrapping, RoundedCorner roundedCorner, Borderline borderline);
+  Shader GetShader(VisualFactoryCache& factoryCache, const ImageVisualShaderFeature::FeatureBuilder& featureBuilder);
 
   /**
-   * Request the default vertex shader source.
+   * @brief Request the default vertex shader source.
    * @return The default vertex shader source.
    */
   std::string_view GetVertexShaderSource();
 
   /**
-   * Request the default fragment shader source.
+   * @brief Request the default fragment shader source.
    * @return The default fragment shader source.
    */
   std::string_view GetFragmentShaderSource();
@@ -116,6 +174,19 @@ protected:
   ImageVisualShaderFactory& operator=(const ImageVisualShaderFactory& rhs);
 
 private:
+
+  /**
+   * @brief Cached information whether native image should change fragment shader.
+   * Default it is ChangeFragmentShader::UNDECIDED.
+   * If we have any chance to check native image source apply fragment shader,
+   * this vaule will be changed one of these : ChangeFragmentShader::DONT_CHANGE or ChangeFragmentShader::NEED_CHANGE
+   *
+   * After result cached, this value will not be changed.
+   *
+   * If value is DONT_CHANGE, ImageVisualShaderFactory::GetShader never call ApplyNativeFragmentShader.
+   * Else, ImageVisualShaderFactory::GetShader will call ApplyNativeFragmentShader if native image source texture come.
+   */
+  ImageVisualShaderFeature::ChangeFragmentShader::Type mFragmentShaderNeedChange : 3;
 };
 
 } // namespace Internal
index 7e404ca..650c508 100644 (file)
@@ -974,59 +974,68 @@ Shader ImageVisual::GenerateShader() const
 {
   Shader shader;
 
-  std::string_view vertexShaderView;
-  bool             usesWholeTexture = true;
-  if(mImpl->mCustomShader && !mImpl->mCustomShader->mVertexShader.empty())
-  {
-    vertexShaderView = mImpl->mCustomShader->mVertexShader;
-    usesWholeTexture = false; // Impossible to tell.
-  }
-  else
-  {
-    vertexShaderView = mImageVisualShaderFactory.GetVertexShaderSource();
-  }
+  bool             usesWholeTexture  = true;
+  const bool       useStandardShader = !mImpl->mCustomShader;
+  const bool       useNativeImage    = (mTextures && DevelTexture::IsNative(mTextures.GetTexture(0)));
 
-  std::string_view fragmentShaderView;
-  if(mImpl->mCustomShader && !mImpl->mCustomShader->mFragmentShader.empty())
-  {
-    fragmentShaderView = mImpl->mCustomShader->mFragmentShader;
-  }
-  else
-  {
-    fragmentShaderView = mImageVisualShaderFactory.GetFragmentShaderSource();
-  }
-
-  // If the texture is native, we may need to change prefix and sampler in
-  // the fragment shader
-  bool        modifiedFragmentShader = false;
-  std::string fragmentShaderString;
-  if(mTextures && DevelTexture::IsNative(mTextures.GetTexture(0)))
-  {
-    Texture nativeTexture  = mTextures.GetTexture(0);
-    fragmentShaderString   = std::string(fragmentShaderView);
-    modifiedFragmentShader = DevelTexture::ApplyNativeFragmentShader(nativeTexture, fragmentShaderString);
-    fragmentShaderView     = fragmentShaderString;
-  }
-
-  const bool useStandardShader = !mImpl->mCustomShader && !modifiedFragmentShader;
   if(useStandardShader)
   {
     // Create and cache the standard shader
     shader = mImageVisualShaderFactory.GetShader(
       mFactoryCache,
-      mImpl->mFlags & Impl::IS_ATLASING_APPLIED ? TextureAtlas::ENABLED : TextureAtlas::DISABLED,
-      mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE ? DefaultTextureWrapMode::APPLY : DefaultTextureWrapMode::DO_NOT_APPLY,
-      IsRoundedCornerRequired() ? RoundedCorner::ENABLED : RoundedCorner::DISABLED,
-      IsBorderlineRequired() ? Borderline::ENABLED : Borderline::DISABLED
+      ImageVisualShaderFeature::FeatureBuilder()
+      .EnableTextureAtlas(mImpl->mFlags & Impl::IS_ATLASING_APPLIED && !useNativeImage)
+      .ApplyDefaultTextureWrapMode(mWrapModeU <= WrapMode::CLAMP_TO_EDGE && mWrapModeV <= WrapMode::CLAMP_TO_EDGE)
+      .EnableRoundedCorner(IsRoundedCornerRequired())
+      .EnableBorderline(IsBorderlineRequired())
+      .SetTextureForFragmentShaderCheck(useNativeImage ? mTextures.GetTexture(0) : Dali::Texture())
     );
   }
-  else if(mImpl->mCustomShader)
-  {
-    shader = Shader::New(vertexShaderView, fragmentShaderView, mImpl->mCustomShader->mHints);
-  }
   else
   {
-    shader = Shader::New(vertexShaderView, fragmentShaderView);
+    std::string_view vertexShaderView;
+    std::string_view fragmentShaderView;
+
+    if(mImpl->mCustomShader && !mImpl->mCustomShader->mVertexShader.empty())
+    {
+      vertexShaderView = mImpl->mCustomShader->mVertexShader;
+      usesWholeTexture = false; // Impossible to tell.
+    }
+    else
+    {
+      vertexShaderView = mImageVisualShaderFactory.GetVertexShaderSource();
+    }
+
+    if(mImpl->mCustomShader && !mImpl->mCustomShader->mFragmentShader.empty())
+    {
+      fragmentShaderView = mImpl->mCustomShader->mFragmentShader;
+    }
+    else
+    {
+      fragmentShaderView = mImageVisualShaderFactory.GetFragmentShaderSource();
+    }
+
+    // If the texture is native, we may need to change prefix and sampler in
+    // the fragment shader
+    if(useNativeImage)
+    {
+      bool        modifiedFragmentShader = false;
+      Texture     nativeTexture          = mTextures.GetTexture(0);
+      std::string fragmentShaderString   = std::string(fragmentShaderView);
+
+      modifiedFragmentShader = DevelTexture::ApplyNativeFragmentShader(nativeTexture, fragmentShaderString);
+      if(modifiedFragmentShader)
+      {
+        fragmentShaderView = fragmentShaderString;
+      }
+
+      // Create shader here cause fragmentShaderString scope issue
+      shader = Shader::New(vertexShaderView, fragmentShaderView, mImpl->mCustomShader->mHints);
+    }
+    else
+    {
+      shader = Shader::New(vertexShaderView, fragmentShaderView, mImpl->mCustomShader->mHints);
+    }
   }
 
   if(usesWholeTexture)
index e87e244..1f48eca 100644 (file)
@@ -378,10 +378,7 @@ void NPatchVisual::OnInitialize()
   Geometry geometry = mFactoryCache.GetGeometry(VisualFactoryCache::QUAD_GEOMETRY);
   Shader   shader   = mImageVisualShaderFactory.GetShader(
     mFactoryCache,
-    TextureAtlas::DISABLED,
-    DefaultTextureWrapMode::APPLY,
-    RoundedCorner::DISABLED,
-    Borderline::DISABLED
+    ImageVisualShaderFeature::FeatureBuilder()
   );
 
   mImpl->mRenderer = Renderer::New(geometry, shader);
index 5d1fff1..25e84fd 100644 (file)
@@ -378,10 +378,10 @@ Shader SvgVisual::GenerateShader() const
   {
     shader = mImageVisualShaderFactory.GetShader(
       mFactoryCache,
-      mAttemptAtlasing ? TextureAtlas::ENABLED : TextureAtlas::DISABLED,
-      DefaultTextureWrapMode::APPLY,
-      IsRoundedCornerRequired() ? RoundedCorner::ENABLED : RoundedCorner::DISABLED,
-      IsBorderlineRequired() ? Borderline::ENABLED : Borderline::DISABLED
+      ImageVisualShaderFeature::FeatureBuilder()
+      .EnableTextureAtlas(mAttemptAtlasing)
+      .EnableRoundedCorner(IsRoundedCornerRequired())
+      .EnableBorderline(IsBorderlineRequired())
     );
   }
   else
index b23c010..deae0b0 100644 (file)
@@ -81,11 +81,15 @@ public:
     GRADIENT_SHADER_RADIAL_USER_SPACE_BORDERLINE,
     GRADIENT_SHADER_RADIAL_USER_SPACE_ROUNDED_BORDERLINE,
     IMAGE_SHADER,
-    IMAGE_SHADER_ATLAS_DEFAULT_WRAP,
-    IMAGE_SHADER_ATLAS_CUSTOM_WRAP,
     IMAGE_SHADER_ROUNDED_CORNER,
     IMAGE_SHADER_BORDERLINE,
     IMAGE_SHADER_ROUNDED_BORDERLINE,
+    IMAGE_SHADER_ATLAS_DEFAULT_WRAP,
+    IMAGE_SHADER_ATLAS_CUSTOM_WRAP,
+    NATIVE_IMAGE_SHADER,
+    NATIVE_IMAGE_SHADER_ROUNDED_CORNER,
+    NATIVE_IMAGE_SHADER_BORDERLINE,
+    NATIVE_IMAGE_SHADER_ROUNDED_BORDERLINE,
     NINE_PATCH_SHADER,
     NINE_PATCH_MASK_SHADER,
     TEXT_SHADER_MULTI_COLOR_TEXT,
index 5f6856e..396764e 100644 (file)
@@ -29,7 +29,7 @@ namespace Toolkit
 {
 const unsigned int TOOLKIT_MAJOR_VERSION = 2;
 const unsigned int TOOLKIT_MINOR_VERSION = 0;
-const unsigned int TOOLKIT_MICRO_VERSION = 52;
+const unsigned int TOOLKIT_MICRO_VERSION = 53;
 const char* const  TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index d9a1c86..c9d2a96 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    2.0.52
+Version:    2.0.53
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT