[dali_2.1.0] Merge branch 'devel/master' 52/267152/1
authorAdam Bialogonski <adam.b@samsung.com>
Fri, 26 Nov 2021 08:42:06 +0000 (08:42 +0000)
committerAdam Bialogonski <adam.b@samsung.com>
Fri, 26 Nov 2021 08:42:06 +0000 (08:42 +0000)
Change-Id: Ic7c5f12a45e5700cf48bc5414f8abeda6c965f8a

71 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-Accessible.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Accessibility-Controls.cpp
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
build/tizen/docs/dali.doxy.in
dali-toolkit/devel-api/controls/accessible-impl.cpp
dali-toolkit/devel-api/controls/accessible-impl.h
dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp [new file with mode: 0644]
dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h [new file with mode: 0644]
dali-toolkit/devel-api/controls/text-controls/text-label-devel.cpp
dali-toolkit/devel-api/controls/text-controls/text-label-devel.h
dali-toolkit/devel-api/file.list
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-anchor-impl.cpp [new file with mode: 0644]
dali-toolkit/internal/controls/text-controls/text-anchor-impl.h [new file with mode: 0644]
dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.h
dali-toolkit/internal/controls/text-controls/text-editor-property-handler.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.h
dali-toolkit/internal/controls/text-controls/text-field-property-handler.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/graphics/shaders/color-visual-shader.frag
dali-toolkit/internal/graphics/shaders/gradient-visual-shader.frag
dali-toolkit/internal/graphics/shaders/image-visual-shader.frag
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-background-actor.cpp [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-background-actor.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-event-handler.cpp
dali-toolkit/internal/text/text-controller-event-handler.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-placeholder-handler.cpp
dali-toolkit/internal/text/text-controller-placeholder-handler.h
dali-toolkit/internal/text/text-controller-relayouter.cpp
dali-toolkit/internal/text/text-controller-relayouter.h
dali-toolkit/internal/text/text-controller-text-updater.cpp
dali-toolkit/internal/text/text-controller-text-updater.h
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/controls/control-impl.cpp
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 423893a..12abee1 100644 (file)
@@ -128,5 +128,16 @@ int UtcDaliAccessibilityCheckShowingState(void)
   states = q->GetStates();
   DALI_TEST_EQUALS((int) states[Dali::Accessibility::State::SHOWING], (int) false, TEST_LOCATION);
 
+  // Make SHOWING parent invisible
+  parentButton.SetProperty(Actor::Property::VISIBLE, false);
+
+  application.SendNotification();
+  application.Render(16);
+
+  q = Dali::Accessibility::Accessible::Get(buttonA);
+  DALI_TEST_CHECK(q);
+  states = q->GetStates();
+  DALI_TEST_EQUALS((int) states[Dali::Accessibility::State::SHOWING], (int) false, TEST_LOCATION);
+
   END_TEST;
-}
\ No newline at end of file
+}
index 9243a0b..a30078a 100644 (file)
@@ -516,6 +516,39 @@ int UtcDaliAccessibilityBloomViewConstructor(void)
 }
 
 #include <dali-toolkit/internal/controls/text-controls/text-field-impl.h>
+#include <dali-toolkit/internal/controls/text-controls/text-anchor-impl.h>
+int UtcDaliAccessibilityTextAnchor(void)
+{
+  ToolkitTestApplication application;
+
+  auto textanchor = TextAnchor::New();
+  DALI_TEST_CHECK( textanchor );
+
+  auto textlabel = TextLabel::New();
+  DALI_TEST_CHECK( textlabel );
+
+  Dali::Accessibility::TestEnableSC( true );
+
+  textlabel.Add(textanchor);
+  auto accessible = Dali::Accessibility::Accessible::Get( textanchor );
+  DALI_TEST_CHECK( accessible );
+  auto hyperlink = dynamic_cast< Dali::Accessibility::Hyperlink* >( accessible );
+  DALI_TEST_CHECK( hyperlink );
+  textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "https://www.tizen.org" );
+  DALI_TEST_EQUALS( hyperlink->IsValid(), true, TEST_LOCATION );
+  auto action = dynamic_cast<Dali::Accessibility::Action*>( accessible );
+  // activation of valid hyperlink
+  DALI_TEST_CHECK( action->DoAction( "accessibilityActivated" ) );
+  // making hyperlink invalid
+  textanchor.SetProperty( Toolkit::TextAnchor::Property::URI, "" );
+  DALI_TEST_EQUALS( hyperlink->IsValid(), false, TEST_LOCATION );
+  DALI_TEST_CHECK( !action->DoAction( "accessibilityActivated" ) );
+
+  Dali::Accessibility::TestEnableSC( false );
+
+  END_TEST;
+}
+
 int UtcDaliAccessibilityTextField(void)
 {
   ToolkitTestApplication application;
@@ -566,6 +599,51 @@ int UtcDaliAccessibilityTextField(void)
   DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION);
   DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION);
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  textfield.SetProperty( Toolkit::TextField::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  textfield.SetProperty( Toolkit::TextField::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
@@ -622,6 +700,51 @@ int UtcDaliAccessibilityTextEditor(void)
   DALI_TEST_EQUALS(editabletext->DeleteText(1, 5), true, TEST_LOCATION);
   DALI_TEST_EQUALS(text->GetText(0, 2), "af", TEST_LOCATION);
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  texteditor.SetProperty( Toolkit::TextEditor::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  texteditor.SetProperty( Toolkit::TextEditor::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
@@ -660,6 +783,51 @@ int UtcDaliAccessibilityTextLabel(void)
   DALI_TEST_EQUALS( text->SetRangeOfSelection( 1, 0, 1 ), false, TEST_LOCATION );
   DALI_TEST_EQUALS( text->RemoveSelection( 1 ), false, TEST_LOCATION );
 
+  auto hypertext = dynamic_cast< Dali::Accessibility::Hypertext* >( accessible );
+  DALI_TEST_CHECK( hypertext );
+  // text without the anchors markup and ENABLE_MARKUP property set (by default) to false
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set (by default) to false
+  textlabel.SetProperty( Toolkit::TextLabel::Property::TEXT, "12345<a href = 'https://www.tizen.org'>anchor1</a>12345<a href = 'https://www.tizen.org' >veryveryveryveryveryveryveryverylonganchor2</a>12345<a href = 'https://www.tizen.org'>anchor3</a>12345" );
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 0 ) == nullptr, true, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLink( 5 ) == nullptr, true, TEST_LOCATION );
+  // text with the anchors markup and ENABLE_MARKUP property set to true
+  textlabel.SetProperty( Toolkit::TextLabel::Property::ENABLE_MARKUP, true);
+  DALI_TEST_EQUALS( hypertext->GetLinkCount(), 3, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( -1 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 0 ), -1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 5 ), 0, TEST_LOCATION ); //1st anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 17 ), 1, TEST_LOCATION ); //2nd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLinkIndex( 66 ), 2, TEST_LOCATION ); //3rd anchor index
+  DALI_TEST_EQUALS( hypertext->GetLink( -1 ) == nullptr, true, TEST_LOCATION );
+  auto hyperlink = hypertext->GetLink( 0 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 5, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 12, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorCount(), 1, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetAnchorUri( 0 ), "https://www.tizen.org", TEST_LOCATION );
+  auto anchorAccessible = hyperlink->GetAnchorAccessible( 0 );
+  DALI_TEST_EQUALS( hyperlink == anchorAccessible, true, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 1 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 17, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 60, TEST_LOCATION );
+  hyperlink = hypertext->GetLink( 2 );
+  DALI_TEST_CHECK ( hyperlink );
+  DALI_TEST_EQUALS( hyperlink->GetStartIndex(), 65, TEST_LOCATION );
+  DALI_TEST_EQUALS( hyperlink->GetEndIndex(), 72, TEST_LOCATION );
+
   Dali::Accessibility::TestEnableSC( false );
 
   END_TEST;
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 c9453e1..b0f9764 100644 (file)
@@ -82,6 +82,8 @@ const unsigned int EMOJI_FONT_SIZE = 3840u; // 60 * 64
 static bool gAnchorClickedCallBackCalled;
 static bool gAnchorClickedCallBackNotCalled;
 
+static bool gTextFitChangedCallBackCalled;
+
 struct CallbackFunctor
 {
   CallbackFunctor(bool* callbackFlag)
@@ -108,6 +110,12 @@ static void TestAnchorClickedCallback(TextLabel control, const char* href, unsig
   }
 }
 
+static void TestTextFitChangedCallback(TextLabel control)
+{
+  tet_infoline(" TestTextFitChangedCallback");
+  gTextFitChangedCallBackCalled = true;
+}
+
 bool DaliTestCheckMaps( const Property::Map& mapGet, const Property::Map& mapSet, const std::vector<std::string>& indexConversionTable = std::vector<std::string>() )
 {
   const Property::Map::SizeType size = mapGet.Count();
@@ -843,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;
 }
 
@@ -1632,6 +1671,13 @@ int UtcDaliToolkitTextlabelTextFit(void)
   label.SetProperty( Actor::Property::SIZE, size );
   label.SetProperty( TextLabel::Property::TEXT, "Hello world" );
 
+   // connect to the text git changed signal.
+  ConnectionTracker* testTracker = new ConnectionTracker();
+  DevelTextLabel::TextFitChangedSignal(label).Connect(&TestTextFitChangedCallback);
+  bool textFitChangedSignal = false;
+  label.ConnectSignal(testTracker, "textFitChanged", CallbackFunctor(&textFitChangedSignal));
+  gTextFitChangedCallBackCalled = false;
+
   // check point size
   Property::Map textFitMapSet;
   textFitMapSet["enable"] = true;
@@ -1651,6 +1697,9 @@ int UtcDaliToolkitTextlabelTextFit(void)
   const Vector3 EXPECTED_NATURAL_SIZE( 450.0f, 96.0f, 0.0f );
   DALI_TEST_EQUALS( EXPECTED_NATURAL_SIZE, label.GetNaturalSize(), TEST_LOCATION );
 
+  DALI_TEST_CHECK(gTextFitChangedCallBackCalled);
+  DALI_TEST_CHECK(textFitChangedSignal);
+
   // check pixel size
   textFitMapSet.Clear();
   textFitMapSet["enable"] = true;
@@ -2027,4 +2076,4 @@ int UtcDaliToolkitTextlabelEllipsisPositionProperty(void)
 
 
   END_TEST;
-}
\ No newline at end of file
+}
index 27a21bc..0fefc0e 100644 (file)
@@ -357,6 +357,7 @@ ALIASES += SINCE_1_3="@since 1.3"
 ALIASES += SINCE_1_4="@since 1.4"
 ALIASES += SINCE_1_9="@since 1.9"
 ALIASES += SINCE_2_0="@since 2.0"
+ALIASES += SINCE_2_1="@since 2.1"
 
 # Extra tags for Tizen 3.0
 ALIASES += SINCE_1_2_2="@since 1.2.2"
@@ -385,6 +386,7 @@ ALIASES += DEPRECATED_1_3_39="@deprecated Deprecated since 1.3.39"
 ALIASES += DEPRECATED_1_3_51="@deprecated Deprecated since 1.3.51"
 ALIASES += DEPRECATED_1_4="@deprecated Deprecated since 1.4"
 ALIASES += DEPRECATED_2_0="@deprecated Deprecated since 2.0"
+ALIASES += DEPRECATED_2_1="@deprecated Deprecated since 2.1"
 
 ALIASES += PLATFORM=""
 ALIASES += PRIVLEVEL_PLATFORM=""
@@ -404,7 +406,8 @@ ALIASES += REMARK_RAWVIDEO=""
 #ALIASES += SINCE_1_3="\par Since:\n 5.0, DALi version 1.3"
 #ALIASES += SINCE_1_4="\par Since:\n 5.5, DALi version 1.4"
 #ALIASES += SINCE_1_9="\par Since:\n 6.0, DALi version 1.9"
-#ALIASES += SINCE_2_0="\par Since:\n 6.0, DALi version 2.0"
+#ALIASES += SINCE_2_0="\par Since:\n 6.5, DALi version 2.0"
+#ALIASES += SINCE_2_1="\par Since:\n 7.0, DALi version 2.1"
 
 ## Extra tags for Tizen 3.0
 #ALIASES += SINCE_1_2_2="\par Since:\n 3.0, DALi version 1.2.2"
@@ -434,7 +437,8 @@ ALIASES += REMARK_RAWVIDEO=""
 #ALIASES += DEPRECATED_1_3_39="@deprecated Deprecated since 5.5, DALi version 1.3.39"
 #ALIASES += DEPRECATED_1_3_51="@deprecated Deprecated since 5.5, DALi version 1.3.51"
 #ALIASES += DEPRECATED_1_4="@deprecated Deprecated since 5.5, DALi version 1.4"
-#ALIASES += DEPRECATED_2_0="@deprecated Deprecated since 5.5, DALi version 2.0"
+#ALIASES += DEPRECATED_2_0="@deprecated Deprecated since 6.5, DALi version 2.0"
+#ALIASES += DEPRECATED_2_1="@deprecated Deprecated since 7.0, DALi version 2.1"
 
 #ALIASES += PLATFORM="@platform"
 #ALIASES += PRIVLEVEL_PLATFORM="\par Privilege Level:\n platform"
index 9f6f4f9..1cf9a26 100644 (file)
@@ -36,7 +36,8 @@
 
 namespace Dali::Toolkit::DevelControl
 {
-
+namespace
+{
 static std::string GetLocaleText(std::string string, const char *domain = "dali-toolkit")
 {
 #ifdef DGETTEXT_ENABLED
@@ -48,6 +49,23 @@ static std::string GetLocaleText(std::string string, const char *domain = "dali-
 #endif
 }
 
+static Dali::Actor CreateHighlightIndicatorActor()
+{
+  std::string focusBorderImagePath(AssetManager::GetDaliImagePath());
+  focusBorderImagePath += "/keyboard_focus.9.png";
+
+  // Create the default if it hasn't been set and one that's shared by all the
+  // keyboard focusable actors
+  auto actor = Toolkit::ImageView::New(focusBorderImagePath);
+  actor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
+
+  DevelControl::AppendAccessibilityAttribute(actor, "highlight", std::string());
+  actor.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, false);
+
+  return actor;
+}
+} // unnamed namespace
+
 AccessibleImpl::AccessibleImpl(Dali::Actor self, Dali::Accessibility::Role role, bool modal)
 : mSelf(self),
   mIsModal(modal)
@@ -203,7 +221,7 @@ std::string AccessibleImpl::GetLocalizedRoleName()
 bool AccessibleImpl::IsShowing()
 {
   Dali::Actor self = Self();
-  if(self.GetProperty(Dali::DevelActor::Property::CULLED).Get<bool>() || !self.GetCurrentProperty<bool>(Actor::Property::VISIBLE))
+  if(!self.GetProperty<bool>(Actor::Property::VISIBLE) || self.GetProperty<Vector4>(Actor::Property::WORLD_COLOR).a == 0 || self.GetProperty<bool>(Dali::DevelActor::Property::CULLED))
   {
     return false;
   }
@@ -219,6 +237,10 @@ bool AccessibleImpl::IsShowing()
   while(parent)
   {
     auto control      = Dali::Toolkit::Control::DownCast(parent->Self());
+    if(!control.GetProperty<bool>(Actor::Property::VISIBLE))
+    {
+      return false;
+    }
     auto clipMode     = control.GetProperty(Actor::Property::CLIPPING_MODE).Get<bool>();
     auto parentExtent = parent->GetExtents(Dali::Accessibility::CoordinateType::WINDOW);
     if ((clipMode != ClippingMode::DISABLED) && !parentExtent.Intersects(childExtent))
@@ -250,7 +272,7 @@ Dali::Accessibility::States AccessibleImpl::CalculateStates()
   state[Dali::Accessibility::State::HIGHLIGHTED] = GetCurrentlyHighlightedActor() == self;
   state[Dali::Accessibility::State::ENABLED]     = true;
   state[Dali::Accessibility::State::SENSITIVE]   = true;
-  state[Dali::Accessibility::State::VISIBLE]     = true;
+  state[Dali::Accessibility::State::VISIBLE]     = self.GetProperty<bool>(Actor::Property::VISIBLE);
 
   if(mIsModal)
   {
@@ -335,22 +357,6 @@ bool AccessibleImpl::GrabFocus()
   return Toolkit::KeyboardFocusManager::Get().SetCurrentFocusActor(Self());
 }
 
-static Dali::Actor CreateHighlightIndicatorActor()
-{
-  std::string focusBorderImagePath(AssetManager::GetDaliImagePath());
-  focusBorderImagePath += "/keyboard_focus.9.png";
-
-  // Create the default if it hasn't been set and one that's shared by all the
-  // keyboard focusable actors
-  auto actor = Toolkit::ImageView::New(focusBorderImagePath);
-  actor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
-
-  DevelControl::AppendAccessibilityAttribute(actor, "highlight", std::string());
-  actor.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, false);
-
-  return actor;
-}
-
 void AccessibleImpl::ScrollToSelf()
 {
   auto* child = this;
index 8bd0714..ea80eef 100644 (file)
@@ -53,7 +53,7 @@ protected:
   bool mIsModal = false;
   bool mIsRoot = false;
 
-  Dali::Actor Self()
+  Dali::Actor Self() const
   {
     auto handle = mSelf.GetHandle();
 
diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.cpp
new file mode 100644 (file)
index 0000000..9b72f8a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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/devel-api/controls/text-controls/text-anchor-devel.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/controls/text-controls/text-anchor-impl.h>
+
+using namespace Dali;
+
+namespace Dali
+{
+namespace Toolkit
+{
+TextAnchor TextAnchor::New()
+{
+  return Internal::TextAnchor::New();
+}
+
+TextAnchor::TextAnchor()
+{
+}
+
+TextAnchor::TextAnchor(const TextAnchor& handle)
+: Control(handle)
+{
+}
+
+TextAnchor& TextAnchor::operator=(const TextAnchor& handle)
+{
+  if(&handle != this)
+  {
+    Control::operator=(handle);
+  }
+  return *this;
+}
+
+TextAnchor::~TextAnchor()
+{
+}
+
+TextAnchor TextAnchor::DownCast(BaseHandle handle)
+{
+  return Control::DownCast<TextAnchor, Internal::TextAnchor>(handle);
+}
+
+TextAnchor::TextAnchor(Internal::TextAnchor& implementation)
+: Control(implementation)
+{
+}
+
+TextAnchor::TextAnchor(Dali::Internal::CustomActor* internal)
+: Control(internal)
+{
+  VerifyCustomActorPointer<Internal::TextAnchor>(internal);
+}
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h
new file mode 100644 (file)
index 0000000..83fbc76
--- /dev/null
@@ -0,0 +1,140 @@
+#ifndef DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H
+#define DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_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/public-api/controls/control.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal DALI_INTERNAL
+{
+class TextAnchor;
+}
+
+/**
+ * @brief A control which renders anchor (hyperlink) in hypertext.
+ */
+class DALI_TOOLKIT_API TextAnchor : public Control
+{
+public:
+  /**
+   * @brief The start and end property ranges for this control.
+   */
+  enum PropertyRange
+  {
+    PROPERTY_START_INDEX = Control::CONTROL_PROPERTY_END_INDEX + 1,
+    PROPERTY_END_INDEX   = PROPERTY_START_INDEX + 1000 ///< Reserve property indices
+  };
+
+  /**
+   * @brief An enumeration of properties belonging to the TextAnchor class.
+   */
+  struct Property
+  {
+    enum
+    {
+      /**
+       * @brief The index of a character in text at which an anchor starts.
+       * @details Name "startCharacterIndex", type INTEGER.
+       */
+      START_CHARACTER_INDEX = PROPERTY_START_INDEX,
+
+      /**
+       * @brief The index of a character in text that stands one position after the anchor's last character.
+       * @details Name "endCharacterIndex", type INTEGER.
+       */
+      END_CHARACTER_INDEX,
+
+      /**
+       * @brief The URI associated with an anchor.
+       * @details Name "uri", type STRING.
+       */
+      URI
+    };
+  };
+
+  /**
+   * @brief Creates the TextAnchor control.
+   * @return A handle to the TextAnchor control.
+   */
+  static TextAnchor New();
+
+  /**
+   * @brief Creates an empty handle.
+   */
+  TextAnchor();
+
+  /**
+   * @brief Copy constructor.
+   *
+   * @param[in] handle The handle to copy from.
+   */
+  TextAnchor(const TextAnchor& handle);
+
+  /**
+   * @brief Assignment operator.
+   *
+   * @param[in] handle The handle to copy from.
+   * @return A reference to this.
+   */
+  TextAnchor& operator=(const TextAnchor& handle);
+
+  /**
+   * @brief Destructor
+   *
+   * This is non-virtual since derived Handle types must not contain data or virtual methods.
+   */
+  ~TextAnchor();
+
+  /**
+   * @brief Downcast a handle to TextAnchor.
+   *
+   * If the BaseHandle points is a TextAnchor the downcast returns a valid handle.
+   * If not the returned handle is left empty.
+   *
+   * @param[in] handle Handle to an object
+   * @return handle to a TextAnchor or an empty handle
+   */
+  static TextAnchor DownCast(BaseHandle handle);
+
+public: // Not intended for application developers
+  /**
+   * @brief Creates a handle using the Toolkit::Internal implementation.
+   *
+   * @param[in] implementation The Control implementation.
+   */
+  DALI_INTERNAL TextAnchor(Internal::TextAnchor& implementation);
+
+  /**
+   * @brief Allows the creation of this Control from an Internal::CustomActor pointer.
+   *
+   * @param[in]  internal  A pointer to the internal CustomActor.
+   */
+  explicit DALI_INTERNAL TextAnchor(Dali::Internal::CustomActor* internal);
+
+}; // Class TextAnchor
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_ANCHOR_DEVEL_H
index 0d6cd0b..c117152 100644 (file)
@@ -30,6 +30,11 @@ AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel)
   return GetImpl(textLabel).AnchorClickedSignal();
 }
 
+TextFitChangedSignalType& TextFitChangedSignal(TextLabel textLabel)
+{
+  return GetImpl(textLabel).TextFitChangedSignal();
+}
+
 } // namespace DevelTextLabel
 
 } // namespace Toolkit
index ac0f30c..6bcbdd0 100644 (file)
@@ -176,6 +176,11 @@ enum Type
 using AnchorClickedSignalType = Signal<void(TextLabel, const char*, uint32_t)>;
 
 /**
+ * @brief TextFit property changed signal type.
+ */
+using TextFitChangedSignalType = Signal<void(TextLabel)>;
+
+/**
  * @brief This signal is emitted when the anchor is clicked.
  *
  * A callback of the following type may be connected:
@@ -187,6 +192,18 @@ using AnchorClickedSignalType = Signal<void(TextLabel, const char*, uint32_t)>;
  */
 DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextLabel textLabel);
 
+/**
+ * @brief This signal is emitted when the textfit property is changed.
+ *
+ * A callback of the following type may be connected:
+ * @code
+ *   void YourCallbackName(TextLabel textLabel);
+ * @endcode
+ * @param[in] textLabel The instance of TextLabel.
+ * @return The signal to connect to.
+ */
+DALI_TOOLKIT_API TextFitChangedSignalType& TextFitChangedSignal(TextLabel textLabel);
+
 } // namespace DevelTextLabel
 
 } // namespace Toolkit
index c88b7a0..3a40bc9 100755 (executable)
@@ -32,6 +32,7 @@ SET( devel_api_src_files
   ${devel_api_src_dir}/controls/shadow-view/shadow-view.cpp
   ${devel_api_src_dir}/controls/super-blur-view/super-blur-view.cpp
   ${devel_api_src_dir}/controls/table-view/table-view.cpp
+  ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-editor-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-field-devel.cpp
   ${devel_api_src_dir}/controls/text-controls/text-label-devel.cpp
@@ -217,6 +218,7 @@ SET( devel_api_super_blur_view_header_files
 )
 
 SET( devel_api_text_controls_header_files
+  ${devel_api_src_dir}/controls/text-controls/text-anchor-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-editor-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-field-devel.h
   ${devel_api_src_dir}/controls/text-controls/text-label-devel.h
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..696b44a 100644 (file)
 #include <dali/public-api/actors/layer.h>
 
 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
 #include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 #include <dali-toolkit/internal/text/text-view.h>
 
 namespace Dali::Toolkit::Internal
 {
+void CommonTextUtils::SynchronizeTextAnchorsInParent(
+  Actor                             parent,
+  Text::ControllerPtr               controller,
+  std::vector<Toolkit::TextAnchor>& anchorActors)
+{
+  for(auto& anchorActor : anchorActors)
+  {
+    parent.Remove(anchorActor);
+  }
+  if(Dali::Accessibility::IsUp())
+  {
+    controller->GetAnchorActors(anchorActors);
+    for(auto& anchorActor : anchorActors)
+    {
+      parent.Add(anchorActor);
+    }
+  }
+}
+
 void CommonTextUtils::RenderText(
-  Actor                            textActor,
-  Text::RendererPtr                renderer,
-  Text::ControllerPtr              controller,
-  Text::DecoratorPtr               decorator,
-  float                            alignmentOffset,
-  Actor&                           renderableActor,
-  Actor&                           backgroundActor,
-  Toolkit::Control&                stencil,
-  std::vector<Actor>&              clippingDecorationActors,
-  Text::Controller::UpdateTextType updateTextType)
+  Actor                             textActor,
+  Text::RendererPtr                 renderer,
+  Text::ControllerPtr               controller,
+  Text::DecoratorPtr                decorator,
+  float&                            alignmentOffset,
+  Actor&                            renderableActor,
+  Actor&                            backgroundActor,
+  Toolkit::Control&                 stencil,
+  std::vector<Actor>&               clippingDecorationActors,
+  std::vector<Toolkit::TextAnchor>& anchorActors,
+  Text::Controller::UpdateTextType  updateTextType)
 {
   Actor newRenderableActor;
 
@@ -126,6 +147,7 @@ void CommonTextUtils::RenderText(
         backgroundActor.LowerToBottom();
       }
     }
+    SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
   }
 }
 
index f2eaba7..212f162 100644 (file)
@@ -16,7 +16,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+#include <dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/rendering/text-renderer.h>
 #include <dali-toolkit/internal/text/text-controller.h>
@@ -37,11 +37,12 @@ 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
    * @param[in,out] clippingDecorationActors Clipping decoration actors
+   * @param[in,out] anchorActors Anchor actors
    * @param[in] updateTextType How the text has been updated
    */
   static void RenderText(
@@ -49,12 +50,24 @@ public:
     Text::RendererPtr                renderer,
     Text::ControllerPtr              controller,
     Text::DecoratorPtr               decorator,
-    float                            alignmentOffset,
+    float&                           alignmentOffset,
     Actor&                           renderableActor,
     Actor&                           backgroundActor,
     Toolkit::Control&                stencil,
     std::vector<Actor>&              clippingDecorationActors,
+    std::vector<Toolkit::TextAnchor>& anchorActors,
     Text::Controller::UpdateTextType updateTextType);
+
+  /**
+   * Common method to synchronize TextAnchor actors with Anchor objects in text's logical model.
+   * @param[in] parent The actor that is a parent of anchor actors
+   * @param[in] controller pointer to the text controller
+   * @param[in,out] anchorActors Anchor actors
+   */
+  static void SynchronizeTextAnchorsInParent(
+    Actor                             parent,
+    Text::ControllerPtr               controller,
+    std::vector<Toolkit::TextAnchor>& anchorActors);
 };
 
 } // namespace Dali::Toolkit::Internal
diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.cpp
new file mode 100644 (file)
index 0000000..2fde91c
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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/text-controls/text-anchor-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/devel-api/object/property-helper-devel.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-common.h>
+#include <dali/public-api/object/type-registry-helper.h>
+
+// INTERNAL INCLUDES
+
+// DEVEL INCLUDES
+#include <dali-toolkit/devel-api/controls/control-devel.h>
+
+using namespace Dali::Toolkit::Text;
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+#endif
+
+// Type registration
+BaseHandle Create()
+{
+  return Toolkit::TextAnchor::New();
+}
+
+// clang-format off
+// Setup properties, signals and actions using the type-registry.
+DALI_TYPE_REGISTRATION_BEGIN(Toolkit::TextAnchor, Toolkit::Control, Create);
+
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "startCharacterIndex", INTEGER, START_CHARACTER_INDEX)
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "endCharacterIndex",   INTEGER, END_CHARACTER_INDEX  )
+DALI_PROPERTY_REGISTRATION(Toolkit, TextAnchor, "uri",                 STRING,  URI        )
+
+DALI_TYPE_REGISTRATION_END()
+// clang-format on
+
+} // namespace
+
+Toolkit::TextAnchor TextAnchor::New()
+{
+  // Create the implementation, temporarily owned by this handle on stack
+  IntrusivePtr<TextAnchor> impl = new TextAnchor();
+
+  // Pass ownership to CustomActor handle
+  Toolkit::TextAnchor handle(*impl);
+
+  // Second-phase init of the implementation
+  // This can only be done after the CustomActor connection has been made...
+  impl->Initialize();
+
+  return handle;
+}
+
+Property::Value TextAnchor::GetProperty(BaseObject* object, Property::Index index)
+{
+  Property::Value value;
+
+  Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object));
+
+  if(anchor)
+  {
+    TextAnchor& impl(GetImpl(anchor));
+
+    switch(index)
+    {
+      case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX:
+      {
+        value = impl.mStartCharacterIndex;
+        break;
+      }
+      case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX:
+      {
+        value = impl.mEndCharacterIndex;
+        break;
+      }
+      case Toolkit::TextAnchor::Property::URI:
+      {
+        value = impl.mUri;
+        break;
+      }
+    }
+  }
+
+  return value;
+}
+
+void TextAnchor::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
+{
+  Toolkit::TextAnchor anchor = Toolkit::TextAnchor::DownCast(Dali::BaseHandle(object));
+
+  if(anchor)
+  {
+    TextAnchor& impl(GetImpl(anchor));
+    switch(index)
+    {
+      case Toolkit::TextAnchor::Property::START_CHARACTER_INDEX:
+      {
+        value.Get(impl.mStartCharacterIndex);
+        break;
+      }
+
+      case Toolkit::TextAnchor::Property::END_CHARACTER_INDEX:
+      {
+        value.Get(impl.mEndCharacterIndex);
+        break;
+      }
+
+      case Toolkit::TextAnchor::Property::URI:
+      {
+        value.Get(impl.mUri);
+        break;
+      }
+    }
+  }
+}
+
+void TextAnchor::OnInitialize()
+{
+  Actor self = Self();
+
+  // Enable highlightability
+  self.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, true);
+
+  DevelControl::SetAccessibilityConstructor(self, [](Dali::Actor actor) {
+    return std::unique_ptr<Dali::Accessibility::Accessible>(
+      new AccessibleImpl(actor, Dali::Accessibility::Role::LINK));
+  });
+}
+
+TextAnchor::TextAnchor()
+: Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT))
+{
+}
+
+TextAnchor::~TextAnchor()
+{
+}
+
+int32_t TextAnchor::AccessibleImpl::GetEndIndex() const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX).Get<int>();
+}
+
+int32_t TextAnchor::AccessibleImpl::GetStartIndex() const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX).Get<int>();
+}
+
+int32_t TextAnchor::AccessibleImpl::GetAnchorCount() const
+{
+  return 1;
+}
+
+Dali::Accessibility::Accessible* TextAnchor::AccessibleImpl::GetAnchorAccessible(int32_t anchorIndex) const
+{
+  return Control::Impl::GetAccessibilityObject(Self());
+}
+
+std::string TextAnchor::AccessibleImpl::GetAnchorUri(int32_t anchorIndex) const
+{
+  auto self = Toolkit::TextAnchor::DownCast(Self());
+  return self.GetProperty(Toolkit::TextAnchor::Property::URI).Get<std::string>();
+}
+
+bool TextAnchor::AccessibleImpl::IsValid() const
+{
+  return !GetAnchorUri(0).empty();
+}
+
+bool TextAnchor::OnAccessibilityActivated()
+{
+  auto uri = Self().GetProperty(Toolkit::TextAnchor::Property::URI).Get<std::string>();
+  if(!uri.empty())
+  {
+    Dali::Actor                                  current                             = Self();
+    Dali::Toolkit::Text::AnchorControlInterface* parentImplementationAnchorInterface = nullptr;
+    while(!current.GetProperty<bool>(Actor::Property::IS_ROOT) && !parentImplementationAnchorInterface)
+    {
+      Dali::Actor            parentAsActor        = current.GetParent();
+      Dali::CustomActor      parentAsCustomActor  = Dali::CustomActor::DownCast(parentAsActor);
+      Dali::CustomActorImpl& parentImplementation = parentAsCustomActor.GetImplementation();
+      parentImplementationAnchorInterface         = dynamic_cast<Dali::Toolkit::Text::AnchorControlInterface*>(&parentImplementation);
+      current                                     = parentAsActor;
+    }
+    if(parentImplementationAnchorInterface)
+    {
+      parentImplementationAnchorInterface->AnchorClicked(uri);
+      return true;
+    }
+    else
+    {
+      DALI_LOG_ERROR("TextAnchor::OnAccessibilityActivate cannot find ancestor actor implementing Dali::Toolkit::Text::AnchorControlInterface.\n");
+    }
+  }
+  return false;
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h b/dali-toolkit/internal/controls/text-controls/text-anchor-impl.h
new file mode 100644 (file)
index 0000000..b590862
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H
+#define DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_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-toolkit/public-api/controls/control-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/controls/text-controls/text-anchor-devel.h>
+#include <dali-toolkit/internal/controls/control/control-data-impl.h>
+#include <dali-toolkit/internal/text/text-anchor-control-interface.h>
+
+namespace Dali
+{
+namespace Toolkit
+{
+namespace Internal
+{
+/**
+ * @brief A control which renders anchor (hyperlink) in hypertext.
+ */
+class TextAnchor : public Control
+{
+public:
+  /**
+   * @copydoc Dali::Toollkit::TextAnchor::New()
+   */
+  static Toolkit::TextAnchor New();
+
+  // Properties
+
+  /**
+   * @brief Called when a property of an object of this type is set.
+   *
+   * @param[in] object The object whose property is set.
+   * @param[in] index The property index.
+   * @param[in] value The new property value.
+   */
+  static void SetProperty(BaseObject* object, Property::Index index, const Property::Value& value);
+
+  /**
+   * @brief Called to retrieve a property of an object of this type.
+   *
+   * @param[in] object The object whose property is to be retrieved.
+   * @param[in] index The property index.
+   * @return The current value of the property.
+   */
+  static Property::Value GetProperty(BaseObject* object, Property::Index index);
+
+private: // From Control
+  /**
+   * @copydoc Control::OnInitialize()
+   */
+  void OnInitialize() override;
+
+  /**
+   * @copydoc Control::OnPropertySet()
+   */
+  // void OnPropertySet(Property::Index index, const Property::Value& propertyValue) override;
+
+  /**
+   * @copydoc Control::OnAccessibilityActivated()
+   */
+  bool OnAccessibilityActivated() override;
+
+private: // Implementation
+  /**
+   * Construct a new TextAnchor.
+   */
+  TextAnchor();
+
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  virtual ~TextAnchor();
+
+private:
+  // Undefined copy constructor and assignment operators
+  TextAnchor(const TextAnchor&);
+  TextAnchor& operator=(const TextAnchor& rhs);
+
+  //Data
+  int         mStartCharacterIndex;
+  int         mEndCharacterIndex;
+  std::string mUri;
+
+protected:
+  /**
+   * @brief This structure is to connect TextAnchor with Accessible functions.
+   */
+  struct AccessibleImpl : public DevelControl::AccessibleImpl,
+                          public virtual Dali::Accessibility::Hyperlink
+  {
+    using DevelControl::AccessibleImpl::AccessibleImpl;
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetEndIndex()
+     */
+    int32_t GetEndIndex() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetStartIndex()
+     */
+    int32_t GetStartIndex() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorCount()
+     */
+    int32_t GetAnchorCount() const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorAccessible()
+     */
+    Accessible* GetAnchorAccessible(int32_t anchorIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::GetAnchorUri()
+     */
+    std::string GetAnchorUri(int32_t anchorIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hyperlink::IsValid()
+     */
+    bool IsValid() const override;
+  };
+};
+
+inline Toolkit::Internal::TextAnchor& GetImpl(Toolkit::TextAnchor& textAnchor)
+{
+  DALI_ASSERT_ALWAYS(textAnchor);
+
+  Dali::RefObject& handle = textAnchor.GetImplementation();
+
+  return static_cast<Toolkit::Internal::TextAnchor&>(handle);
+}
+
+inline const Toolkit::Internal::TextAnchor& GetImpl(const Toolkit::TextAnchor& textAnchor)
+{
+  DALI_ASSERT_ALWAYS(textAnchor);
+
+  const Dali::RefObject& handle = textAnchor.GetImplementation();
+
+  return static_cast<const Toolkit::Internal::TextAnchor&>(handle);
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_INTERNAL_TEXT_ANCHOR_H
index 0259471..44c1176 100644 (file)
@@ -478,6 +478,11 @@ Toolkit::TextEditor::ScrollStateChangedSignalType& TextEditor::ScrollStateChange
   return mScrollStateChangedSignal;
 }
 
+void TextEditor::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 void TextEditor::OnInitialize()
 {
   Actor self = Self();
@@ -568,6 +573,9 @@ void TextEditor::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextEditor::OnAccessibilityStatusChanged);
 }
 
 void TextEditor::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -724,7 +732,7 @@ void TextEditor::OnRelayout(const Vector2& size, RelayoutContainer& container)
 
 void TextEditor::RenderText(Text::Controller::UpdateTextType updateTextType)
 {
-  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType);
+  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType);
   if(mRenderableActor)
   {
     ApplyScrollPosition();
@@ -1540,6 +1548,30 @@ bool TextEditor::AccessibleImpl::SetTextContents(std::string newContents)
   return true;
 }
 
+int32_t TextEditor::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextEditor::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextEditor::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextEditor::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextEditor::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextEditor::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index 8c5d7be..e4b5fa5 100644 (file)
@@ -473,6 +473,9 @@ private: // Implementation
   // Connection needed to re-render text, when a text editor returns to the scene.
   void OnSceneConnect(Dali::Actor actor);
 
+  // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model
+  void OnAccessibilityStatusChanged();
+
 private: // Data
   // Signals
   Toolkit::TextEditor::TextChangedSignalType                mTextChangedSignal;
@@ -485,17 +488,18 @@ private: // Data
   Toolkit::DevelTextEditor::SelectionChangedSignalType      mSelectionChangedSignal;
   Toolkit::DevelTextEditor::SelectionClearedSignalType      mSelectionClearedSignal;
 
-  InputMethodContext            mInputMethodContext;
-  Text::ControllerPtr           mController;
-  Text::RendererPtr             mRenderer;
-  Text::DecoratorPtr            mDecorator;
-  Text::TextVerticalScrollerPtr mTextVerticalScroller;
-  Toolkit::Control              mStencil;
-  Toolkit::ScrollBar            mScrollBar;
-  Dali::Animation               mAnimation; ///< Scroll indicator Show/Hide Animation.
-  Dali::TimePeriod              mAnimationPeriod;
-  std::vector<Actor>            mClippingDecorationActors; ///< Decoration actors which need clipping.
-  Dali::InputMethodOptions      mInputMethodOptions;
+  InputMethodContext               mInputMethodContext;
+  Text::ControllerPtr              mController;
+  Text::RendererPtr                mRenderer;
+  Text::DecoratorPtr               mDecorator;
+  Text::TextVerticalScrollerPtr    mTextVerticalScroller;
+  Toolkit::Control                 mStencil;
+  Toolkit::ScrollBar               mScrollBar;
+  Dali::Animation                  mAnimation; ///< Scroll indicator Show/Hide Animation.
+  Dali::TimePeriod                 mAnimationPeriod;
+  std::vector<Actor>               mClippingDecorationActors; ///< Decoration actors which need clipping.
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+  Dali::InputMethodOptions         mInputMethodOptions;
 
   Actor         mRenderableActor;
   Actor         mActiveLayer;
@@ -529,7 +533,8 @@ private: // Data
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
                           public virtual Dali::Accessibility::Text,
-                          public virtual Dali::Accessibility::EditableText
+                          public virtual Dali::Accessibility::EditableText,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -607,6 +612,21 @@ private: // Data
      * @copydoc Dali::Accessibility::EditableText::DeleteText()
      */
     bool DeleteText(size_t startPosition, size_t endPosition) override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index 62dfbda..f35c169 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include <dali-toolkit/internal/controls/text-controls/text-editor-property-handler.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 
 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
 
@@ -311,6 +312,7 @@ void TextEditor::PropertyHandler::SetProperty(Toolkit::TextEditor textEditor, Pr
       DALI_LOG_INFO(gTextEditorLogFilter, Debug::General, "TextEditor %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup);
 
       impl.mController->SetMarkupProcessorEnabled(enableMarkup);
+      CommonTextUtils::SynchronizeTextAnchorsInParent(textEditor, impl.mController, impl.mAnchorActors);
       break;
     }
     case Toolkit::TextEditor::Property::INPUT_COLOR:
index fe01e46..2f4ef60 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()
@@ -441,6 +463,11 @@ DevelTextField::SelectionClearedSignalType& TextField::SelectionClearedSignal()
   return mSelectionClearedSignal;
 }
 
+void TextField::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 void TextField::OnInitialize()
 {
   Actor self = Self();
@@ -519,6 +546,9 @@ void TextField::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::ENTRY));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextField::OnAccessibilityStatusChanged);
 }
 
 void TextField::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -680,7 +710,7 @@ Text::ControllerPtr TextField::GetTextController()
 
 void TextField::RenderText(Text::Controller::UpdateTextType updateTextType)
 {
-  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, updateTextType);
+  CommonTextUtils::RenderText(Self(), mRenderer, mController, mDecorator, mAlignmentOffset, mRenderableActor, mBackgroundActor, mStencil, mClippingDecorationActors, mAnchorActors, updateTextType);
 }
 
 void TextField::OnKeyInputFocusGained()
@@ -1101,6 +1131,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 +1153,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 +1194,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 +1297,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)
@@ -1369,6 +1422,30 @@ bool TextField::AccessibleImpl::SetTextContents(std::string newContents)
   return true;
 }
 
+int32_t TextField::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextField::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextField::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextField::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextField::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextField::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index b65e953..7e18a82 100644 (file)
@@ -429,6 +429,9 @@ private: // Implementation
   // Connection needed to re-render text, when a Text Field returns to the scene.
   void OnSceneConnect(Dali::Actor actor);
 
+  // Needed to synchronize TextAnchor actors with Anchor objects in text's logical model
+  void OnAccessibilityStatusChanged();
+
 private: // Data
   // Signals
   Toolkit::TextField::TextChangedSignalType                mTextChangedSignal;
@@ -440,13 +443,14 @@ private: // Data
   Toolkit::DevelTextField::SelectionChangedSignalType      mSelectionChangedSignal;
   Toolkit::DevelTextField::SelectionClearedSignalType      mSelectionClearedSignal;
 
-  InputMethodContext       mInputMethodContext;
-  Text::ControllerPtr      mController;
-  Text::RendererPtr        mRenderer;
-  Text::DecoratorPtr       mDecorator;
-  Toolkit::Control         mStencil;                  ///< For EXCEED_POLICY_CLIP
-  std::vector<Actor>       mClippingDecorationActors; ///< Decoration actors which need clipping.
-  Dali::InputMethodOptions mInputMethodOptions;
+  InputMethodContext               mInputMethodContext;
+  Text::ControllerPtr              mController;
+  Text::RendererPtr                mRenderer;
+  Text::DecoratorPtr               mDecorator;
+  Toolkit::Control                 mStencil;                  ///< For EXCEED_POLICY_CLIP
+  std::vector<Actor>               mClippingDecorationActors; ///< Decoration actors which need clipping.
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+  Dali::InputMethodOptions         mInputMethodOptions;
 
   Actor         mRenderableActor;
   Actor         mActiveLayer;
@@ -477,7 +481,8 @@ protected:
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
                           public virtual Dali::Accessibility::Text,
-                          public virtual Dali::Accessibility::EditableText
+                          public virtual Dali::Accessibility::EditableText,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -555,6 +560,21 @@ protected:
      * @copydoc Dali::Accessibility::EditableText::DeleteText()
      */
     bool DeleteText(size_t startPosition, size_t endPosition) override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index 04a5082..dc253f0 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include <dali-toolkit/internal/controls/text-controls/text-field-property-handler.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 
 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
 #include <dali-toolkit/devel-api/text/rendering-backend.h>
@@ -408,6 +409,7 @@ void TextField::PropertyHandler::SetProperty(Toolkit::TextField textField, Prope
       DALI_LOG_INFO(gTextFieldLogFilter, Debug::General, "TextField %p ENABLE_MARKUP %d\n", impl.mController.Get(), enableMarkup);
 
       impl.mController->SetMarkupProcessorEnabled(enableMarkup);
+      CommonTextUtils::SynchronizeTextAnchorsInParent(textField, impl.mController, impl.mAnchorActors);
       break;
     }
     case Toolkit::TextField::Property::INPUT_FONT_FAMILY:
@@ -507,6 +509,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 21e13c8..22990d9 100644 (file)
@@ -37,6 +37,7 @@
 #include <dali-toolkit/internal/text/text-effects-style.h>
 #include <dali-toolkit/internal/text/text-font-style.h>
 #include <dali-toolkit/internal/text/text-view.h>
+#include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
 #include <dali-toolkit/public-api/text/text-enumerations.h>
 
 #include <dali-toolkit/devel-api/controls/control-devel.h>
@@ -79,6 +80,7 @@ const std::string TEXT_FIT_ENABLE_KEY("enable");
 const std::string TEXT_FIT_MIN_SIZE_KEY("minSize");
 const std::string TEXT_FIT_MAX_SIZE_KEY("maxSize");
 const std::string TEXT_FIT_STEP_SIZE_KEY("stepSize");
+const std::string TEXT_FIT_FONT_SIZE_KEY("fontSize");
 const std::string TEXT_FIT_FONT_SIZE_TYPE_KEY("fontSizeType");
 
 #if defined(DEBUG_ENABLED)
@@ -143,6 +145,7 @@ DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColo
 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION(Toolkit,    TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3)
 
 DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "anchorClicked", SIGNAL_ANCHOR_CLICKED)
+DALI_SIGNAL_REGISTRATION(Toolkit, TextLabel, "textFitChanged", SIGNAL_TEXT_FIT_CHANGED)
 
 DALI_TYPE_REGISTRATION_END()
 // clang-format on
@@ -485,6 +488,7 @@ void TextLabel::SetProperty(BaseObject* object, Property::Index index, const Pro
       case Toolkit::DevelTextLabel::Property::TEXT_FIT:
       {
         ParseTextFitProperty(impl.mController, value.GetMap());
+        impl.mController->SetTextFitChanged(true);
         break;
       }
       case Toolkit::DevelTextLabel::Property::MIN_LINE_SIZE:
@@ -729,12 +733,14 @@ Property::Value TextLabel::GetProperty(BaseObject* object, Property::Index index
         const float minSize  = impl.mController->GetTextFitMinSize();
         const float maxSize  = impl.mController->GetTextFitMaxSize();
         const float stepSize = impl.mController->GetTextFitStepSize();
+        const float pointSize = impl.mController->GetTextFitPointSize();
 
         Property::Map map;
         map.Insert(TEXT_FIT_ENABLE_KEY, enabled);
         map.Insert(TEXT_FIT_MIN_SIZE_KEY, minSize);
         map.Insert(TEXT_FIT_MAX_SIZE_KEY, maxSize);
         map.Insert(TEXT_FIT_STEP_SIZE_KEY, stepSize);
+        map.Insert(TEXT_FIT_FONT_SIZE_KEY, pointSize);
         map.Insert(TEXT_FIT_FONT_SIZE_TYPE_KEY, "pointSize");
 
         value = map;
@@ -776,6 +782,14 @@ bool TextLabel::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface*
       labelImpl.AnchorClickedSignal().Connect(tracker, functor);
     }
   }
+  else if(0 == strcmp(signalName.c_str(), SIGNAL_TEXT_FIT_CHANGED))
+  {
+    if(label)
+    {
+      Internal::TextLabel& labelImpl(GetImpl(label));
+      labelImpl.TextFitChangedSignal().Connect(tracker, functor);
+    }
+  }
   else
   {
     // signalName does not match any signal
@@ -790,6 +804,11 @@ DevelTextLabel::AnchorClickedSignalType& TextLabel::AnchorClickedSignal()
   return mAnchorClickedSignal;
 }
 
+DevelTextLabel::TextFitChangedSignalType& TextLabel::TextFitChangedSignal()
+{
+  return mTextFitChangedSignal;
+}
+
 void TextLabel::OnInitialize()
 {
   Actor self = Self();
@@ -832,6 +851,9 @@ void TextLabel::OnInitialize()
     return std::unique_ptr<Dali::Accessibility::Accessible>(
       new AccessibleImpl(actor, Dali::Accessibility::Role::LABEL));
   });
+
+  Accessibility::Bridge::EnabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged);
+  Accessibility::Bridge::DisabledSignal().Connect(this, &TextLabel::OnAccessibilityStatusChanged);
 }
 
 void TextLabel::OnStyleChange(Toolkit::StyleManager styleManager, StyleChange::Type change)
@@ -919,6 +941,12 @@ void TextLabel::OnPropertySet(Property::Index index, const Property::Value& prop
       }
       break;
     }
+    case Toolkit::TextLabel::Property::TEXT:
+    case Toolkit::TextLabel::Property::ENABLE_MARKUP:
+    {
+      CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+      break;
+    }
     default:
     {
       Control::OnPropertySet(index, propertyValue); // up call to control for non-handled properties
@@ -997,6 +1025,12 @@ void TextLabel::OnRelayout(const Vector2& size, RelayoutContainer& container)
 
     mTextUpdateNeeded = false;
   }
+
+  if(mController->IsTextFitChanged())
+  {
+    EmitTextFitChangedSignal();
+    mController->SetTextFitChanged(false);
+  }
 }
 
 void TextLabel::RequestTextRelayout()
@@ -1080,6 +1114,17 @@ void TextLabel::OnLayoutDirectionChanged(Actor actor, LayoutDirection::Type type
   mController->ChangedLayoutDirection();
 }
 
+void TextLabel::EmitTextFitChangedSignal()
+{
+  Dali::Toolkit::TextLabel handle(GetOwner());
+  mTextFitChangedSignal.Emit(handle);
+}
+
+void TextLabel::OnAccessibilityStatusChanged()
+{
+  CommonTextUtils::SynchronizeTextAnchorsInParent(Self(), mController, mAnchorActors);
+}
+
 TextLabel::TextLabel()
 : Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
   mRenderingBackend(DEFAULT_RENDERING_BACKEND),
@@ -1140,8 +1185,8 @@ bool TextLabel::AccessibleImpl::SetCursorOffset(size_t offset)
 
 Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t offset, Dali::Accessibility::TextBoundary boundary)
 {
-  auto self = Toolkit::TextLabel::DownCast(Self());
-  auto text = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get<std::string>();
+  auto self     = Toolkit::TextLabel::DownCast(Self());
+  auto text     = self.GetProperty(Toolkit::TextLabel::Property::TEXT).Get<std::string>();
   auto textSize = text.size();
 
   auto range = Dali::Accessibility::Range{};
@@ -1162,7 +1207,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off
     case Dali::Accessibility::TextBoundary::LINE:
     {
       auto textString = text.c_str();
-      auto breaks = std::vector<char>(textSize, 0);
+      auto breaks     = std::vector<char>(textSize, 0);
 
       if(boundary == Dali::Accessibility::TextBoundary::WORD)
       {
@@ -1173,7 +1218,7 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetTextAtOffset(size_t off
         Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(textString), textSize, "", breaks.data());
       }
 
-      auto index = 0u;
+      auto index   = 0u;
       auto counter = 0u;
       while(index < textSize && counter <= offset)
       {
@@ -1237,8 +1282,8 @@ Dali::Accessibility::Range TextLabel::AccessibleImpl::GetRangeOfSelection(size_t
     return {};
   }
 
-  auto self  = Toolkit::TextLabel::DownCast(Self());
-  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  auto        self       = Toolkit::TextLabel::DownCast(Self());
+  auto        controller = Dali::Toolkit::GetImpl(self).GetTextController();
   std::string value{};
   controller->RetrieveSelection(value);
   auto indices = controller->GetSelectionIndexes();
@@ -1272,6 +1317,30 @@ bool TextLabel::AccessibleImpl::SetRangeOfSelection(size_t selectionIndex, size_
   return true;
 }
 
+int32_t TextLabel::AccessibleImpl::GetLinkCount() const
+{
+  auto self = Toolkit::TextLabel::DownCast(Self());
+  return Dali::Toolkit::GetImpl(self).mAnchorActors.size();
+}
+
+Accessibility::Hyperlink* TextLabel::AccessibleImpl::GetLink(int32_t linkIndex) const
+{
+  if(linkIndex < 0 || linkIndex >= GetLinkCount())
+  {
+    return nullptr;
+  }
+  auto self        = Toolkit::TextLabel::DownCast(Self());
+  auto anchorActor = Dali::Toolkit::GetImpl(self).mAnchorActors[linkIndex];
+  return dynamic_cast<Accessibility::Hyperlink*>(Dali::Accessibility::Accessible::Get(anchorActor));
+}
+
+int32_t TextLabel::AccessibleImpl::GetLinkIndex(int32_t characterOffset) const
+{
+  auto self       = Toolkit::TextLabel::DownCast(Self());
+  auto controller = Dali::Toolkit::GetImpl(self).GetTextController();
+  return controller->GetAnchorIndex(static_cast<size_t>(characterOffset));
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index 01e003b..5c9a6b1 100644 (file)
@@ -76,6 +76,11 @@ public:
   DevelTextLabel::AnchorClickedSignalType& AnchorClickedSignal();
 
   /**
+   * @copydoc Dali::Toollkit::TextLabel::TextFitChangedSignal()
+   */
+  DevelTextLabel::TextFitChangedSignalType& TextFitChangedSignal();
+
+  /**
    * Connects a callback function with the object's signals.
    * @param[in] object The object providing the signal.
    * @param[in] tracker Used to disconnect the signal.
@@ -189,14 +194,23 @@ private:
    */
   void OnLayoutDirectionChanged(Actor actor, LayoutDirection::Type type);
 
+  /**
+   * @brief Emits TextFitChanged signal.
+   */
+  void EmitTextFitChangedSignal();
+  void OnAccessibilityStatusChanged();
+
 private: // Data
   Text::ControllerPtr   mController;
   Text::TextScrollerPtr mTextScroller;
 
   Toolkit::Visual::Base mVisual;
 
+  std::vector<Toolkit::TextAnchor> mAnchorActors;
+
   // Signals
   Toolkit::DevelTextLabel::AnchorClickedSignalType mAnchorClickedSignal;
+  Toolkit::DevelTextLabel::TextFitChangedSignalType mTextFitChangedSignal;
 
   int  mRenderingBackend;
   bool mTextUpdateNeeded : 1;
@@ -206,7 +220,8 @@ protected:
    * @brief This structure is to connect TextLabel with Accessible functions.
    */
   struct AccessibleImpl : public DevelControl::AccessibleImpl,
-                          public virtual Dali::Accessibility::Text
+                          public virtual Dali::Accessibility::Text,
+                          public virtual Dali::Accessibility::Hypertext
   {
     using DevelControl::AccessibleImpl::AccessibleImpl;
 
@@ -259,6 +274,21 @@ protected:
      * @copydoc Dali::Accessibility::Text::GetNamePropertyIndex()
      */
     Property::Index GetNamePropertyIndex() override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLink()
+     */
+    Accessibility::Hyperlink* GetLink(int32_t linkIndex) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkIndex()
+     */
+    int32_t GetLinkIndex(int32_t characterOffset) const override;
+
+    /**
+     * @copydoc Dali::Accessibility::Hypertext::GetLinkCount()
+     */
+    int32_t GetLinkCount() const override;
   };
 };
 
index 3229006..8438261 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
@@ -101,6 +102,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/controls/super-blur-view/super-blur-view-impl.cpp
    ${toolkit_src_dir}/controls/table-view/table-view-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/common-text-utils.cpp
+   ${toolkit_src_dir}/controls/text-controls/text-anchor-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/text-editor-impl.cpp
    ${toolkit_src_dir}/controls/text-controls/text-editor-property-handler.cpp
    ${toolkit_src_dir}/controls/text-controls/text-field-impl.cpp
@@ -156,11 +158,14 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/text/hyphenator.cpp
    ${toolkit_src_dir}/text/text-enumerations-impl.cpp
    ${toolkit_src_dir}/text/text-controller.cpp
+   ${toolkit_src_dir}/text/text-controller-background-actor.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 +207,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}
index 3e3d19d..33202a2 100644 (file)
@@ -273,21 +273,26 @@ void main()
   if(abs(vPosition.x) < vOptRectSize.x && abs(vPosition.y) < vOptRectSize.y)
   {
     OUT_COLOR = targetColor;
-    return;
   }
-  PreprocessPotential();
+  else
+  {
+    PreprocessPotential();
 #endif
 
 #if !IS_REQUIRED_BLUR && IS_REQUIRED_BORDERLINE
-  targetColor = convertBorderlineColor(targetColor);
+    targetColor = convertBorderlineColor(targetColor);
 #endif
-  OUT_COLOR = targetColor;
+    OUT_COLOR = targetColor;
 
 #if IS_REQUIRED_BLUR
-  mediump float opacity = calculateBlurOpacity();
-  OUT_COLOR.a *= opacity;
+    mediump float opacity = calculateBlurOpacity();
+    OUT_COLOR.a *= opacity;
 #elif IS_REQUIRED_ROUNDED_CORNER
-  mediump float opacity = calculateCornerOpacity();
-  OUT_COLOR.a *= opacity;
+    mediump float opacity = calculateCornerOpacity();
+    OUT_COLOR.a *= opacity;
+#endif
+
+#if IS_REQUIRED_BLUR || IS_REQUIRED_ROUNDED_CORNER || IS_REQUIRED_BORDERLINE
+  }
 #endif
 }
index 80090f8..a30bf5e 100644 (file)
@@ -207,18 +207,23 @@ void main()
   if(abs(vPosition.x) < vOptRectSize.x && abs(vPosition.y) < vOptRectSize.y)
   {
     OUT_COLOR = textureColor;
-    return;
   }
-  PreprocessPotential();
+  else
+  {
+    PreprocessPotential();
 #endif
 
 #if IS_REQUIRED_BORDERLINE
-  textureColor = convertBorderlineColor(textureColor);
+    textureColor = convertBorderlineColor(textureColor);
 #endif
-  OUT_COLOR = textureColor;
+    OUT_COLOR = textureColor;
 
 #if IS_REQUIRED_ROUNDED_CORNER
-  mediump float opacity = calculateCornerOpacity();
-  OUT_COLOR *= opacity;
+    mediump float opacity = calculateCornerOpacity();
+    OUT_COLOR *= opacity;
+#endif
+
+#if IS_REQUIRED_ROUNDED_CORNER || IS_REQUIRED_BORDERLINE
+  }
 #endif
 }
index 37f080f..703c649 100644 (file)
@@ -233,19 +233,24 @@ void main()
   if(abs(vPosition.x) < vOptRectSize.x && abs(vPosition.y) < vOptRectSize.y)
   {
     OUT_COLOR = textureColor;
-    return;
   }
-  PreprocessPotential();
+  else
+  {
+    PreprocessPotential();
 #endif
 
 #if IS_REQUIRED_BORDERLINE
-  textureColor = convertBorderlineColor(textureColor);
+    textureColor = convertBorderlineColor(textureColor);
 #endif
-  OUT_COLOR = textureColor;
+    OUT_COLOR = textureColor;
 
 #if IS_REQUIRED_ROUNDED_CORNER
-  mediump float opacity = calculateCornerOpacity();
-  OUT_COLOR.a *= opacity;
-  OUT_COLOR.rgb *= mix(1.0, opacity, preMultipliedAlpha);
+    mediump float opacity = calculateCornerOpacity();
+    OUT_COLOR.a *= opacity;
+    OUT_COLOR.rgb *= mix(1.0, opacity, preMultipliedAlpha);
+#endif
+
+#if IS_REQUIRED_ROUNDED_CORNER || IS_REQUIRED_BORDERLINE
+  }
 #endif
 }
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-background-actor.cpp b/dali-toolkit/internal/text/text-controller-background-actor.cpp
new file mode 100644 (file)
index 0000000..dda514f
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ *
+ */
+
+// HEADER
+#include <dali-toolkit/internal/text/text-controller-background-actor.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/rendering/renderer.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
+#include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
+#include <dali-toolkit/internal/text/text-view.h>
+
+namespace Dali::Toolkit::Text
+{
+
+namespace
+{
+struct BackgroundVertex
+{
+  Vector2 mPosition; ///< Vertex posiiton
+  Vector4 mColor;    ///< Vertex color
+};
+
+struct BackgroundMesh
+{
+  Vector<BackgroundVertex> mVertices; ///< container of vertices
+  Vector<unsigned short>   mIndices;  ///< container of indices
+};
+} // unnamed namespace
+
+Actor CreateControllerBackgroundActor(const View& textView, const VisualModelPtr& textVisualModel, Shader& textShaderBackground)
+{
+  // NOTE: Currently we only support background color for left-to-right text.
+
+  Actor actor;
+
+  Length numberOfGlyphs = textView.GetNumberOfGlyphs();
+  if(numberOfGlyphs > 0u)
+  {
+    Vector<GlyphInfo> glyphs;
+    glyphs.Resize(numberOfGlyphs);
+
+    Vector<Vector2> positions;
+    positions.Resize(numberOfGlyphs);
+
+    // Get the line where the glyphs are laid-out.
+    const LineRun* lineRun         = textVisualModel->mLines.Begin();
+    float          alignmentOffset = lineRun->alignmentOffset;
+    numberOfGlyphs                 = textView.GetGlyphs(glyphs.Begin(),
+                                                     positions.Begin(),
+                                                     alignmentOffset,
+                                                     0u,
+                                                     numberOfGlyphs);
+
+    glyphs.Resize(numberOfGlyphs);
+    positions.Resize(numberOfGlyphs);
+
+    const GlyphInfo* const glyphsBuffer    = glyphs.Begin();
+    const Vector2* const   positionsBuffer = positions.Begin();
+
+    BackgroundMesh mesh;
+    mesh.mVertices.Reserve(4u * glyphs.Size());
+    mesh.mIndices.Reserve(6u * glyphs.Size());
+
+    const Vector2 textSize = textView.GetLayoutSize();
+
+    const float offsetX = alignmentOffset + textSize.width * 0.5f;
+    const float offsetY = textSize.height * 0.5f;
+
+    const Vector4* const    backgroundColorsBuffer       = textView.GetBackgroundColors();
+    const ColorIndex* const backgroundColorIndicesBuffer = textView.GetBackgroundColorIndices();
+    const Vector4&          defaultBackgroundColor       = textVisualModel->IsBackgroundEnabled() ? textVisualModel->GetBackgroundColor() : Color::TRANSPARENT;
+
+    Vector4   quad;
+    uint32_t  numberOfQuads = 0u;
+    Length    yLineOffset   = 0;
+    Length    prevLineIndex = 0;
+    LineIndex lineIndex;
+    Length    numberOfLines;
+
+    for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
+    {
+      const GlyphInfo& glyph = *(glyphsBuffer + i);
+
+      // Get the background color of the character.
+      // The color index zero is reserved for the default background color (i.e. Color::TRANSPARENT)
+      const bool       isMarkupBackground       = textView.IsMarkupBackgroundColorSet();
+      const ColorIndex backgroundColorIndex     = isMarkupBackground ? *(backgroundColorIndicesBuffer + i) : 0u;
+      const bool       isDefaultBackgroundColor = (0u == backgroundColorIndex);
+      const Vector4&   backgroundColor          = isDefaultBackgroundColor ? defaultBackgroundColor : *(backgroundColorsBuffer + backgroundColorIndex - 1u);
+
+      textVisualModel->GetNumberOfLines(i, 1, lineIndex, numberOfLines);
+      Length lineHeight = lineRun[lineIndex].ascender + -(lineRun[lineIndex].descender) + lineRun[lineIndex].lineSpacing;
+
+      if(lineIndex != prevLineIndex)
+      {
+        yLineOffset += lineHeight;
+      }
+
+      // Only create quads for glyphs with a background color
+      if(backgroundColor != Color::TRANSPARENT)
+      {
+        const Vector2 position = *(positionsBuffer + i);
+
+        if(i == 0u && glyphSize == 1u) // Only one glyph in the whole text
+        {
+          quad.x = position.x;
+          quad.y = yLineOffset;
+          quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
+          quad.w = lineHeight;
+        }
+        else if((lineIndex != prevLineIndex) || (i == 0u)) // The first glyph in the line
+        {
+          quad.x = position.x;
+          quad.y = yLineOffset;
+          quad.z = quad.x - glyph.xBearing + glyph.advance;
+          quad.w = quad.y + lineHeight;
+        }
+        else if(i == glyphSize - 1u) // The last glyph in the whole text
+        {
+          quad.x = position.x - glyph.xBearing;
+          quad.y = yLineOffset;
+          quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
+          quad.w = quad.y + lineHeight;
+        }
+        else // The glyph in the middle of the text
+        {
+          quad.x = position.x - glyph.xBearing;
+          quad.y = yLineOffset;
+          quad.z = quad.x + glyph.advance;
+          quad.w = quad.y + lineHeight;
+        }
+
+        BackgroundVertex vertex;
+
+        // Top left
+        vertex.mPosition.x = quad.x - offsetX;
+        vertex.mPosition.y = quad.y - offsetY;
+        vertex.mColor      = backgroundColor;
+        mesh.mVertices.PushBack(vertex);
+
+        // Top right
+        vertex.mPosition.x = quad.z - offsetX;
+        vertex.mPosition.y = quad.y - offsetY;
+        vertex.mColor      = backgroundColor;
+        mesh.mVertices.PushBack(vertex);
+
+        // Bottom left
+        vertex.mPosition.x = quad.x - offsetX;
+        vertex.mPosition.y = quad.w - offsetY;
+        vertex.mColor      = backgroundColor;
+        mesh.mVertices.PushBack(vertex);
+
+        // Bottom right
+        vertex.mPosition.x = quad.z - offsetX;
+        vertex.mPosition.y = quad.w - offsetY;
+        vertex.mColor      = backgroundColor;
+        mesh.mVertices.PushBack(vertex);
+
+        // Six indices in counter clockwise winding
+        mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
+        mesh.mIndices.PushBack(0u + 4 * numberOfQuads);
+        mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
+        mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
+        mesh.mIndices.PushBack(3u + 4 * numberOfQuads);
+        mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
+
+        numberOfQuads++;
+      }
+
+      if(lineIndex != prevLineIndex)
+      {
+        prevLineIndex = lineIndex;
+      }
+    }
+
+    // Only create the background actor if there are glyphs with background color
+    if(mesh.mVertices.Count() > 0u)
+    {
+      Property::Map quadVertexFormat;
+      quadVertexFormat["aPosition"] = Property::VECTOR2;
+      quadVertexFormat["aColor"]    = Property::VECTOR4;
+
+      VertexBuffer quadVertices = VertexBuffer::New(quadVertexFormat);
+      quadVertices.SetData(&mesh.mVertices[0], mesh.mVertices.Size());
+
+      Geometry quadGeometry = Geometry::New();
+      quadGeometry.AddVertexBuffer(quadVertices);
+      quadGeometry.SetIndexBuffer(&mesh.mIndices[0], mesh.mIndices.Size());
+
+      if(!textShaderBackground)
+      {
+        textShaderBackground = Shader::New(SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_VERT, SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_FRAG);
+      }
+
+      Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, textShaderBackground);
+      renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
+      renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT);
+
+      actor = Actor::New();
+      actor.SetProperty(Dali::Actor::Property::NAME, "TextBackgroundColorActor");
+      actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+      actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+      actor.SetProperty(Actor::Property::SIZE, textSize);
+      actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
+      actor.AddRenderer(renderer);
+    }
+  }
+
+  return actor;
+}
+
+} // namespace Dali::Toolkit::Text
diff --git a/dali-toolkit/internal/text/text-controller-background-actor.h b/dali-toolkit/internal/text/text-controller-background-actor.h
new file mode 100644 (file)
index 0000000..5ad9084
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef DALI_TOOLKIT_TEXT_CONTROLLER_BACKGROUND_ACTOR_H
+#define DALI_TOOLKIT_TEXT_CONTROLLER_BACKGROUND_ACTOR_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/actors/actor.h>
+#include <dali/public-api/rendering/shader.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/visual-model-impl.h>
+
+namespace Dali::Toolkit::Text
+{
+class View;
+
+/**
+ * @brief Create an actor that renders the text background color
+ *
+ * @return the created actor or an empty handle if no background color needs to be rendered.
+ */
+Actor CreateControllerBackgroundActor(const View& textView, const VisualModelPtr& textVisualModel, Shader& textShaderBackground);
+
+} // namespace Dali::Toolkit::Text
+
+#endif // DALI_TOOLKIT_TEXT_CONTROLLER_BACKGROUND_ACTOR_H
index 5d78405..7494cb6 100644 (file)
@@ -26,6 +26,8 @@
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
 #include <dali-toolkit/internal/text/text-controller-impl.h>
+#include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
+#include <dali-toolkit/internal/text/text-controller-text-updater.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
 
 namespace
@@ -66,7 +68,7 @@ void Controller::EventHandler::KeyboardFocusGainEvent(Controller& controller)
     if(controller.mImpl->IsShowingPlaceholderText())
     {
       // Show alternative placeholder-text when editing
-      controller.ShowPlaceholderText();
+      PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
     }
 
     controller.mImpl->RequestRelayout();
@@ -102,7 +104,7 @@ void Controller::EventHandler::KeyboardFocusLostEvent(Controller& controller)
       if(!controller.mImpl->IsShowingRealText())
       {
         // Revert to regular placeholder-text when not editing
-        controller.ShowPlaceholderText();
+        PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
       }
     }
   }
@@ -237,7 +239,7 @@ bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyE
     else if((Dali::DALI_KEY_BACKSPACE == keyCode) ||
             (Dali::DevelKey::DALI_KEY_DELETE == keyCode))
     {
-      textChanged = controller.DeleteEvent(keyCode);
+      textChanged = DeleteEvent(controller, keyCode);
 
       // Will request for relayout.
       relayoutNeeded = true;
@@ -302,7 +304,7 @@ bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyE
         // InputMethodContext is no longer handling key-events
         controller.mImpl->ClearPreEditFlag();
 
-        controller.InsertText(refinedKey, COMMIT);
+        TextUpdater::InsertText(controller, refinedKey, COMMIT);
 
         textChanged = true;
 
@@ -419,7 +421,7 @@ void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tap
         if(controller.mImpl->IsShowingPlaceholderText() && !controller.mImpl->IsFocusedPlaceholderAvailable())
         {
           // Hide placeholder text
-          controller.ResetText();
+          TextUpdater::ResetText(controller);
         }
 
         if(EventData::INACTIVE == state)
@@ -598,18 +600,18 @@ void Controller::EventHandler::ProcessModifyEvents(Controller& controller)
       // A (single) replace event should come first, otherwise we wasted time processing NOOP events
       DALI_ASSERT_DEBUG(it == events.Begin() && "Unexpected TEXT_REPLACED event");
 
-      controller.TextReplacedEvent();
+      TextReplacedEvent(controller);
     }
     else if(ModifyEvent::TEXT_INSERTED == event.type)
     {
-      controller.TextInsertedEvent();
+      TextInsertedEvent(controller);
     }
     else if(ModifyEvent::TEXT_DELETED == event.type)
     {
       // Placeholder-text cannot be deleted
       if(!controller.mImpl->IsShowingPlaceholderText())
       {
-        controller.TextDeletedEvent();
+        TextDeletedEvent(controller);
       }
     }
   }
@@ -711,22 +713,18 @@ bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
 
   if(EventData::SELECTING == controller.mImpl->mEventData->mState)
   {
-    removed = controller.RemoveSelectedText();
+    removed = TextUpdater::RemoveSelectedText(controller);
   }
   else if((controller.mImpl->mEventData->mPrimaryCursorPosition > 0) && (keyCode == Dali::DALI_KEY_BACKSPACE))
   {
     // Remove the character before the current cursor position
-    removed = controller.RemoveText(-1,
-                                    1,
-                                    UPDATE_INPUT_STYLE);
+    removed = TextUpdater::RemoveText(controller, -1, 1, UPDATE_INPUT_STYLE);
   }
   else if((controller.mImpl->mEventData->mPrimaryCursorPosition < controller.mImpl->mModel->mLogicalModel->mText.Count()) &&
           (keyCode == Dali::DevelKey::DALI_KEY_DELETE))
   {
     // Remove the character after the current cursor position
-    removed = controller.RemoveText(0,
-                                    1,
-                                    UPDATE_INPUT_STYLE);
+    removed = TextUpdater::RemoveText(controller, 0, 1, UPDATE_INPUT_STYLE);
   }
 
   if(removed)
@@ -738,7 +736,7 @@ bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
     }
     else
     {
-      controller.ShowPlaceholderText();
+      PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
     }
     controller.mImpl->mEventData->mUpdateCursorPosition = true;
     controller.mImpl->mEventData->mScrollAfterDelete    = true;
@@ -760,23 +758,24 @@ InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextE
   {
     case InputMethodContext::COMMIT:
     {
-      controller.InsertText(inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
+      TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
       requestRelayout = true;
       retrieveCursor  = true;
       break;
     }
     case InputMethodContext::PRE_EDIT:
     {
-      controller.InsertText(inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
+      TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
       requestRelayout = true;
       retrieveCursor  = true;
       break;
     }
     case InputMethodContext::DELETE_SURROUNDING:
     {
-      const bool textDeleted = controller.RemoveText(inputMethodContextEvent.cursorOffset,
-                                                     inputMethodContextEvent.numberOfChars,
-                                                     DONT_UPDATE_INPUT_STYLE);
+      const bool textDeleted = TextUpdater::RemoveText(controller,
+                                                       inputMethodContextEvent.cursorOffset,
+                                                       inputMethodContextEvent.numberOfChars,
+                                                       DONT_UPDATE_INPUT_STYLE);
 
       if(textDeleted)
       {
@@ -787,7 +786,7 @@ InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextE
         }
         else
         {
-          controller.ShowPlaceholderText();
+          PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
         }
         controller.mImpl->mEventData->mUpdateCursorPosition = true;
         controller.mImpl->mEventData->mScrollAfterDelete    = true;
@@ -882,7 +881,7 @@ void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller)
   controller.mImpl->SetClipboardHideEnable(false);
 
   // Paste
-  controller.PasteText(stringToPaste);
+  TextUpdater::PasteText(controller, stringToPaste);
 
   controller.mImpl->SetClipboardHideEnable(true);
 }
@@ -972,14 +971,14 @@ void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Da
       if(controller.mImpl->mEventData->mSelectionEnabled)
       {
         // Creates a SELECT event.
-        controller.SelectEvent(currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
+        SelectEvent(controller, currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
       }
       break;
     }
     case Toolkit::TextSelectionPopup::SELECT_ALL:
     {
       // Creates a SELECT_ALL event
-      controller.SelectEvent(0.f, 0.f, SelectionType::ALL);
+      SelectEvent(controller, 0.f, 0.f, SelectionType::ALL);
       break;
     }
     case Toolkit::TextSelectionPopup::CLIPBOARD:
index 6e6e068..0028890 100644 (file)
@@ -37,19 +37,82 @@ namespace Text
 {
 struct Controller::EventHandler
 {
+  /// @copydoc Text::Controller::KeyboardFocusGainEvent
+  /// @param[in] controller A reference to the controller class
   static void KeyboardFocusGainEvent(Controller& controller);
+
+  /// @copydoc Text::Controller::KeyboardFocusLostEvent
+  /// @param[in] controller A reference to the controller class
   static void KeyboardFocusLostEvent(Controller& controller);
+
+  /// @copydoc Text::Controller::KeyEvent
+  /// @param[in] controller A reference to the controller class
   static bool KeyEvent(Controller& controller, const Dali::KeyEvent& keyEvent);
+
+  /// @copydoc Text::Controller::AnchorEvent
+  /// @param[in] controller A reference to the controller class
   static void AnchorEvent(Controller& controller, float x, float y);
+
+  /// @copydoc Text::Controller::TapEvent
+  /// @param[in] controller A reference to the controller class
   static void TapEvent(Controller& controller, unsigned int tapCount, float x, float y);
+
+  /// @copydoc Text::Controller::PanEvent
+  /// @param[in] controller A reference to the controller class
   static void PanEvent(Controller& controller, GestureState state, const Vector2& displacement);
+
+  /// @copydoc Text::Controller::LongPressEvent
+  /// @param[in] controller A reference to the controller class
   static void LongPressEvent(Controller& controller, GestureState state, float x, float y);
+
+  /// @copydoc Text::Controller::SelectEvent
+  /// @param[in] controller A reference to the controller class
   static void SelectEvent(Controller& controller, float x, float y, SelectionType selectType);
+
+  /**
+   * @brief Creates a selection event with a selection index.
+   *
+   * It could be called from the SelectText().
+   * The start and end parameters are passed through the event.
+   *
+   * @param[in] controller A reference to the controller class
+   * @param[in] start The start selection position.
+   * @param[in] end The end selection position.
+   * @param[in] selection type like the range.
+   */
   static void SelectEvent(Controller& controller, const uint32_t start, const uint32_t end, SelectionType selectType);
+
+  /**
+   * @brief Process queued events which modify the model.
+   * @param[in] controller A reference to the controller class
+   */
   static void ProcessModifyEvents(Controller& controller);
+
+  /**
+   * @brief Used to process an event queued from SetText()
+   * @param[in] controller A reference to the controller class
+   */
   static void TextReplacedEvent(Controller& controller);
+
+  /**
+   * @brief Used to process an event queued from key events etc.
+   * @param[in] controller A reference to the controller class
+   */
   static void TextInsertedEvent(Controller& controller);
+
+  /**
+   * @brief Used to process an event queued from backspace key etc.
+   * @param[in] controller A reference to the controller class
+   */
   static void TextDeletedEvent(Controller& controller);
+
+  /**
+   * @brief Helper to KeyEvent() to handle the backspace or delete key case.
+   *
+   * @param[in] controller A reference to the controller class
+   * @param[in] keyCode The keycode for the key pressed
+   * @return True if a character was deleted.
+   */
   static bool DeleteEvent(Controller& controller, int keyCode);
 
   static InputMethodContext::CallbackData OnInputMethodContextEvent(Controller&                          controller,
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..35ad959 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/public-api/actors/layer.h>
+#include <dali/devel-api/adaptor-framework/window-devel.h>
 
 // INTERNAL INCLUDES
-#include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
-#include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
 #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-controller-placeholder-handler.h>
+#include <dali-toolkit/internal/text/text-controller-relayouter.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
 #include <dali-toolkit/internal/text/text-run-container.h>
@@ -43,17 +46,9 @@ namespace
 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
 #endif
 
-struct BackgroundVertex
-{
-  Vector2 mPosition; ///< Vertex posiiton
-  Vector4 mColor;    ///< Vertex color
-};
+constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
 
-struct BackgroundMesh
-{
-  Vector<BackgroundVertex> mVertices; ///< container of vertices
-  Vector<unsigned short>   mIndices;  ///< container of indices
-};
+const std::string EMPTY_STRING("");
 
 } // namespace
 
@@ -129,6 +124,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)
@@ -256,6 +488,19 @@ Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
   return numberOfWhiteSpaces;
 }
 
+void Controller::Impl::GetText(std::string& text) const
+{
+  if(!IsShowingPlaceholderText())
+  {
+    // Retrieves the text string.
+    GetText(0u, text);
+  }
+  else
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
+  }
+}
+
 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
 {
   // Get the total number of characters.
@@ -268,6 +513,61 @@ 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>());
+  }
+}
+
+Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
+{
+  if(mUpdateTextDirection)
+  {
+    // Operations that can be done only once until the text changes.
+    const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
+                                                                          GET_SCRIPTS |
+                                                                          VALIDATE_FONTS |
+                                                                          GET_LINE_BREAKS |
+                                                                          BIDI_INFO |
+                                                                          SHAPE_TEXT |
+                                                                          GET_GLYPH_METRICS);
+
+    // Set the update info to relayout the whole text.
+    mTextUpdateInfo.mParagraphCharacterIndex     = 0u;
+    mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
+
+    // Make sure the model is up-to-date before layouting
+    UpdateModel(onlyOnceOperations);
+
+    Vector3 naturalSize;
+    Relayouter::DoRelayout(*this,
+                           Size(MAX_FLOAT, MAX_FLOAT),
+                           static_cast<OperationsMask>(onlyOnceOperations |
+                                                       LAYOUT | REORDER | UPDATE_DIRECTION),
+                           naturalSize.GetVectorXY());
+
+    // Do not do again the only once operations.
+    mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
+
+    // Clear the update info. This info will be set the next time the text is updated.
+    mTextUpdateInfo.Clear();
+
+    // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
+    mTextUpdateInfo.mFullRelayoutNeeded = true;
+
+    mUpdateTextDirection = false;
+  }
+
+  return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
+}
+
 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
 {
   mTextUpdateInfo.mParagraphCharacterIndex = 0u;
@@ -353,315 +653,122 @@ void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
   mTextUpdateInfo.mStartGlyphIndex             = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
 }
 
-void Controller::Impl::ClearFullModelData(OperationsMask operations)
+void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
 {
-  if(NO_OPERATION != (GET_LINE_BREAKS & operations))
-  {
-    mModel->mLogicalModel->mLineBreakInfo.Clear();
-    mModel->mLogicalModel->mParagraphInfo.Clear();
-  }
+  ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
+}
 
-  if(NO_OPERATION != (GET_SCRIPTS & operations))
-  {
-    mModel->mLogicalModel->mScriptRuns.Clear();
-  }
+bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
+{
+  return ControllerImplModelUpdater::Update(*this, operationsRequired);
+}
 
-  if(NO_OPERATION != (VALIDATE_FONTS & operations))
-  {
-    mModel->mLogicalModel->mFontRuns.Clear();
-  }
+void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
+{
+  SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
+}
 
-  if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count())
+float Controller::Impl::GetDefaultFontLineHeight()
+{
+  FontId defaultFontId = 0u;
+  if(nullptr == mFontDefaults)
   {
-    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();
-    }
+    TextAbstraction::FontDescription fontDescription;
+    defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale);
   }
-
-  if(NO_OPERATION != (SHAPE_TEXT & operations))
+  else
   {
-    mModel->mVisualModel->mGlyphs.Clear();
-    mModel->mVisualModel->mGlyphsToCharacters.Clear();
-    mModel->mVisualModel->mCharactersToGlyph.Clear();
-    mModel->mVisualModel->mCharactersPerGlyph.Clear();
-    mModel->mVisualModel->mGlyphsPerCharacter.Clear();
-    mModel->mVisualModel->mGlyphPositions.Clear();
+    defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
   }
 
-  if(NO_OPERATION != (LAYOUT & operations))
-  {
-    mModel->mVisualModel->mLines.Clear();
-  }
+  Text::FontMetrics fontMetrics;
+  mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
 
-  if(NO_OPERATION != (COLOR & operations))
-  {
-    mModel->mVisualModel->mColorIndices.Clear();
-    mModel->mVisualModel->mBackgroundColorIndices.Clear();
-  }
+  return (fontMetrics.ascender - fontMetrics.descender);
 }
 
-void Controller::Impl::ClearCharacterModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
+bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
 {
-  const CharacterIndex endIndexPlusOne = endIndex + 1u;
-
-  if(NO_OPERATION != (GET_LINE_BREAKS & operations))
+  if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
   {
-    // Clear the line break info.
-    LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin();
+    mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
+    mRecalculateNaturalSize = true;
 
-    mModel->mLogicalModel->mLineBreakInfo.Erase(lineBreakInfoBuffer + startIndex,
-                                                lineBreakInfoBuffer + endIndexPlusOne);
-
-    // Clear the paragraphs.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mParagraphInfo);
+    RelayoutForNewLineSize();
+    return true;
   }
+  return false;
+}
 
-  if(NO_OPERATION != (GET_SCRIPTS & operations))
+bool Controller::Impl::SetDefaultLineSize(float lineSize)
+{
+  if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
   {
-    // Clear the scripts.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mScriptRuns);
-  }
+    mLayoutEngine.SetDefaultLineSize(lineSize);
+    mRecalculateNaturalSize = true;
 
-  if(NO_OPERATION != (VALIDATE_FONTS & operations))
-  {
-    // Clear the fonts.
-    ClearCharacterRuns(startIndex,
-                       endIndex,
-                       mModel->mLogicalModel->mFontRuns);
+    RelayoutForNewLineSize();
+    return true;
   }
+  return false;
+}
 
-  if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count())
+string Controller::Impl::GetSelectedText()
+{
+  string text;
+  if(EventData::SELECTING == mEventData->mState)
   {
-    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);
-    }
+    RetrieveSelection(text, false);
   }
+  return text;
 }
 
-void Controller::Impl::ClearGlyphModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
+string Controller::Impl::CopyText()
 {
-  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);
+  string text;
+  RetrieveSelection(text, false);
+  SendSelectionToClipboard(false); // Text not modified
 
-    // Clear the glyphs buffer.
-    GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin();
-    mModel->mVisualModel->mGlyphs.Erase(glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex,
-                                        glyphsBuffer + endGlyphIndexPlusOne);
+  mEventData->mUpdateCursorPosition = true;
 
-    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;
-    }
+  RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
 
-    // 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);
+  return text;
+}
 
-    // 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);
-    }
-  }
+string Controller::Impl::CutText()
+{
+  string text;
+  RetrieveSelection(text, false);
 
-  if(NO_OPERATION != (LAYOUT & operations))
+  if(!IsEditable())
   {
-    // 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);
+    return EMPTY_STRING;
   }
 
-  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);
-    }
-  }
-}
+  SendSelectionToClipboard(true); // Synchronous call to modify text
+  mOperationsPending = ALL_OPERATIONS;
 
-void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
-{
-  if(mTextUpdateInfo.mClearAll ||
-     ((0u == startIndex) &&
-      (mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u)))
+  if((0u != mModel->mLogicalModel->mText.Count()) ||
+     !IsPlaceholderAvailable())
   {
-    ClearFullModelData(operations);
+    QueueModifyEvent(ModifyEvent::TEXT_DELETED);
   }
   else
   {
-    // Clear the model data related with characters.
-    ClearCharacterModelData(startIndex, endIndex, operations);
-
-    // Clear the model data related with glyphs.
-    ClearGlyphModelData(startIndex, endIndex, operations);
+    PlaceholderHandler::ShowPlaceholderText(*this);
   }
 
-  // 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();
-}
-
-bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
-{
-  return ControllerImplModelUpdater::Update(*this, operationsRequired);
-}
+  mEventData->mUpdateCursorPosition = true;
+  mEventData->mScrollAfterDelete    = true;
 
-void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
-{
-  SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
-}
+  RequestRelayout();
 
-float Controller::Impl::GetDefaultFontLineHeight()
-{
-  FontId defaultFontId = 0u;
-  if(nullptr == mFontDefaults)
-  {
-    TextAbstraction::FontDescription fontDescription;
-    defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale);
-  }
-  else
+  if(nullptr != mEditableControlInterface)
   {
-    defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
+    mEditableControlInterface->TextChanged(true);
   }
-
-  Text::FontMetrics fontMetrics;
-  mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
-
-  return (fontMetrics.ascender - fontMetrics.descender);
+  return text;
 }
 
 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
@@ -783,6 +890,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();
   }
 }
 
@@ -948,277 +1075,62 @@ void Controller::Impl::SetPopupButtons()
   bool                        isEditable    = IsEditable();
   TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
 
-  if(EventData::SELECTING == mEventData->mState)
-  {
-    buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
-    if(isEditable)
-    {
-      buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
-    }
-
-    if(!IsClipboardEmpty())
-    {
-      if(isEditable)
-      {
-        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
-      }
-      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
-    }
-
-    if(!mEventData->mAllTextSelected)
-    {
-      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
-    }
-  }
-  else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
-  {
-    if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
-    {
-      buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
-    }
-
-    if(!IsClipboardEmpty())
-    {
-      if(isEditable)
-      {
-        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
-      }
-      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
-    }
-  }
-  else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
-  {
-    if(!IsClipboardEmpty())
-    {
-      if(isEditable)
-      {
-        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
-      }
-      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
-    }
-  }
-
-  mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
-}
-
-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);
+  if(EventData::SELECTING == mEventData->mState)
+  {
+    buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
+    if(isEditable)
+    {
+      buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
+    }
 
-        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:
+    if(!IsClipboardEmpty())
+    {
+      if(isEditable)
       {
-        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;
+        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
       }
-      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();
-        }
+      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
+    }
 
-        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->mAllTextSelected)
+    {
+      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
+    }
+  }
+  else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
+  {
+    if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
+    {
+      buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
+    }
 
-        if(mEventData->mGrabHandlePopupEnabled)
-        {
-          SetPopupButtons();
-          mEventData->mDecorator->SetPopupActive(true);
-        }
-        mEventData->mDecoratorUpdated = true;
-        break;
+    if(!IsClipboardEmpty())
+    {
+      if(isEditable)
+      {
+        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
       }
-      case EventData::TEXT_PANNING:
+      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
+    }
+  }
+  else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
+  {
+    if(!IsClipboardEmpty())
+    {
+      if(isEditable)
       {
-        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;
+        buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
       }
+      buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
     }
   }
+
+  mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
+}
+
+void Controller::Impl::ChangeState(EventData::State newState)
+{
+  ChangeTextControllerState(*this, newState);
 }
 
 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
@@ -1550,186 +1462,138 @@ void Controller::Impl::RequestRelayout()
   }
 }
 
-Actor Controller::Impl::CreateBackgroundActor()
+void Controller::Impl::RelayoutForNewLineSize()
 {
-  // NOTE: Currently we only support background color for left-to-right text.
+  // relayout all characters
+  mTextUpdateInfo.mCharacterIndex             = 0;
+  mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
+  mTextUpdateInfo.mNumberOfCharactersToAdd    = mModel->mLogicalModel->mText.Count();
+  mOperationsPending                          = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
 
-  Actor actor;
-
-  Length numberOfGlyphs = mView.GetNumberOfGlyphs();
-  if(numberOfGlyphs > 0u)
+  //remove selection
+  if(mEventData && mEventData->mState == EventData::SELECTING)
   {
-    Vector<GlyphInfo> glyphs;
-    glyphs.Resize(numberOfGlyphs);
-
-    Vector<Vector2> positions;
-    positions.Resize(numberOfGlyphs);
-
-    // Get the line where the glyphs are laid-out.
-    const LineRun* lineRun         = mModel->mVisualModel->mLines.Begin();
-    float          alignmentOffset = lineRun->alignmentOffset;
-    numberOfGlyphs                 = mView.GetGlyphs(glyphs.Begin(),
-                                     positions.Begin(),
-                                     alignmentOffset,
-                                     0u,
-                                     numberOfGlyphs);
-
-    glyphs.Resize(numberOfGlyphs);
-    positions.Resize(numberOfGlyphs);
+    ChangeState(EventData::EDITING);
+  }
 
-    const GlyphInfo* const glyphsBuffer    = glyphs.Begin();
-    const Vector2* const   positionsBuffer = positions.Begin();
+  RequestRelayout();
+}
 
-    BackgroundMesh mesh;
-    mesh.mVertices.Reserve(4u * glyphs.Size());
-    mesh.mIndices.Reserve(6u * glyphs.Size());
+bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
+{
+  return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
+}
 
-    const Vector2 textSize = mView.GetLayoutSize();
+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); } );
+    }
 
-    const float offsetX = alignmentOffset + textSize.width * 0.5f;
-    const float offsetY = textSize.height * 0.5f;
+    mEventData->mInputStyleChangedQueue.Clear();
+  }
+}
 
-    const Vector4* const    backgroundColorsBuffer       = mView.GetBackgroundColors();
-    const ColorIndex* const backgroundColorIndicesBuffer = mView.GetBackgroundColorIndices();
-    const Vector4&          defaultBackgroundColor       = mModel->mVisualModel->IsBackgroundEnabled() ? mModel->mVisualModel->GetBackgroundColor() : Color::TRANSPARENT;
+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;
 
-    Vector4   quad;
-    uint32_t  numberOfQuads = 0u;
-    Length    yLineOffset   = 0;
-    Length    prevLineIndex = 0;
-    LineIndex lineIndex;
-    Length    numberOfLines;
+    scroll.x = -scroll.x;
+    scroll.y = -scroll.y;
 
-    for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
+    if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
     {
-      const GlyphInfo& glyph = *(glyphsBuffer + i);
-
-      // Get the background color of the character.
-      // The color index zero is reserved for the default background color (i.e. Color::TRANSPARENT)
-      const bool       isMarkupBackground       = mView.IsMarkupBackgroundColorSet();
-      const ColorIndex backgroundColorIndex     = isMarkupBackground ? *(backgroundColorIndicesBuffer + i) : 0u;
-      const bool       isDefaultBackgroundColor = (0u == backgroundColorIndex);
-      const Vector4&   backgroundColor          = isDefaultBackgroundColor ? defaultBackgroundColor : *(backgroundColorsBuffer + backgroundColorIndex - 1u);
-
-      mModel->mVisualModel->GetNumberOfLines(i, 1, lineIndex, numberOfLines);
-      Length lineHeight = lineRun[lineIndex].ascender + -(lineRun[lineIndex].descender) + lineRun[lineIndex].lineSpacing;
+      mModel->mScrollPosition.x += scroll.x;
+      ClampHorizontalScroll(layoutSize);
+    }
 
-      if(lineIndex != prevLineIndex)
-      {
-        yLineOffset += lineHeight;
-      }
+    if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
+    {
+      mModel->mScrollPosition.y += scroll.y;
+      ClampVerticalScroll(layoutSize);
+    }
 
-      // Only create quads for glyphs with a background color
-      if(backgroundColor != Color::TRANSPARENT)
-      {
-        const Vector2 position = *(positionsBuffer + i);
+    if(mModel->mScrollPosition != currentScroll)
+    {
+      mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
+      RequestRelayout();
+    }
+  }
+}
 
-        if(i == 0u && glyphSize == 1u) // Only one glyph in the whole text
-        {
-          quad.x = position.x;
-          quad.y = yLineOffset;
-          quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
-          quad.w = lineHeight;
-        }
-        else if((lineIndex != prevLineIndex) || (i == 0u)) // The first glyph in the line
-        {
-          quad.x = position.x;
-          quad.y = yLineOffset;
-          quad.z = quad.x - glyph.xBearing + glyph.advance;
-          quad.w = quad.y + lineHeight;
-        }
-        else if(i == glyphSize - 1u) // The last glyph in the whole text
-        {
-          quad.x = position.x - glyph.xBearing;
-          quad.y = yLineOffset;
-          quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
-          quad.w = quad.y + lineHeight;
-        }
-        else // The glyph in the middle of the text
-        {
-          quad.x = position.x - glyph.xBearing;
-          quad.y = yLineOffset;
-          quad.z = quad.x + glyph.advance;
-          quad.w = quad.y + lineHeight;
-        }
+float Controller::Impl::GetHorizontalScrollPosition()
+{
+  // Scroll values are negative internally so we convert them to positive numbers
+  return mEventData ? -mModel->mScrollPosition.x : 0.0f;
+}
 
-        BackgroundVertex vertex;
-
-        // Top left
-        vertex.mPosition.x = quad.x - offsetX;
-        vertex.mPosition.y = quad.y - offsetY;
-        vertex.mColor      = backgroundColor;
-        mesh.mVertices.PushBack(vertex);
-
-        // Top right
-        vertex.mPosition.x = quad.z - offsetX;
-        vertex.mPosition.y = quad.y - offsetY;
-        vertex.mColor      = backgroundColor;
-        mesh.mVertices.PushBack(vertex);
-
-        // Bottom left
-        vertex.mPosition.x = quad.x - offsetX;
-        vertex.mPosition.y = quad.w - offsetY;
-        vertex.mColor      = backgroundColor;
-        mesh.mVertices.PushBack(vertex);
-
-        // Bottom right
-        vertex.mPosition.x = quad.z - offsetX;
-        vertex.mPosition.y = quad.w - offsetY;
-        vertex.mColor      = backgroundColor;
-        mesh.mVertices.PushBack(vertex);
-
-        // Six indices in counter clockwise winding
-        mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
-        mesh.mIndices.PushBack(0u + 4 * numberOfQuads);
-        mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
-        mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
-        mesh.mIndices.PushBack(3u + 4 * numberOfQuads);
-        mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
-
-        numberOfQuads++;
-      }
+float Controller::Impl::GetVerticalScrollPosition()
+{
+  // Scroll values are negative internally so we convert them to positive numbers
+  return mEventData ? -mModel->mScrollPosition.y : 0.0f;
+}
 
-      if(lineIndex != prevLineIndex)
-      {
-        prevLineIndex = lineIndex;
-      }
-    }
+Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
+{
+  //TODO
+  return Vector3(10.f, 10.f, 10.f);
+}
 
-    // Only create the background actor if there are glyphs with background color
-    if(mesh.mVertices.Count() > 0u)
-    {
-      Property::Map quadVertexFormat;
-      quadVertexFormat["aPosition"] = Property::VECTOR2;
-      quadVertexFormat["aColor"]    = Property::VECTOR4;
+Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
+{
+  //TODO
+  return Vector2(10.f, 10.f);
+}
 
-      VertexBuffer quadVertices = VertexBuffer::New(quadVertexFormat);
-      quadVertices.SetData(&mesh.mVertices[0], mesh.mVertices.Size());
+Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
+{
+  auto actor = Toolkit::TextAnchor::New();
+  actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
+  actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
+  const Vector3 anchorPosition = GetAnchorPosition(anchor);
+  actor.SetProperty(Actor::Property::POSITION, anchorPosition);
+  const Vector2 anchorSize = GetAnchorSize(anchor);
+  actor.SetProperty(Actor::Property::SIZE, anchorSize);
+  std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
+  actor.SetProperty(Actor::Property::NAME, anchorText);
+  actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
+  actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
+  actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
+  return actor;
+}
 
-      Geometry quadGeometry = Geometry::New();
-      quadGeometry.AddVertexBuffer(quadVertices);
-      quadGeometry.SetIndexBuffer(&mesh.mIndices[0], mesh.mIndices.Size());
+void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
+{
+  /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
+           we need to create and destroy potentially many actors. Some optimization can be considered here.
+           Maybe a "dirty" flag in mLogicalModel? */
+  anchorActors.clear();
+  for(auto& anchor : mModel->mLogicalModel->mAnchors)
+  {
+    auto actor = CreateAnchorActor(anchor);
+    anchorActors.push_back(actor);
+  }
+}
 
-      if(!mShaderBackground)
-      {
-        mShaderBackground = Shader::New(SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_VERT, SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_FRAG);
-      }
+int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
+{
+  Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
 
-      Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, mShaderBackground);
-      renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
-      renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT);
-
-      actor = Actor::New();
-      actor.SetProperty(Dali::Actor::Property::NAME, "TextBackgroundColorActor");
-      actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
-      actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
-      actor.SetProperty(Actor::Property::SIZE, textSize);
-      actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
-      actor.AddRenderer(renderer);
-    }
+  while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
+  {
+    it++;
   }
 
-  return actor;
+  return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
 }
 
 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
@@ -1758,4 +1622,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 0c6297f..3c72bdb 100644 (file)
@@ -356,8 +356,9 @@ struct Controller::Impl
     mTextFitMinSize(DEFAULT_TEXTFIT_MIN),
     mTextFitMaxSize(DEFAULT_TEXTFIT_MAX),
     mTextFitStepSize(DEFAULT_TEXTFIT_STEP),
-    mTextFitEnabled(false),
     mFontSizeScale(DEFAULT_FONT_SIZE_SCALE),
+    mTextFitEnabled(false),
+    mTextFitChanged(false),
     mIsLayoutDirectionChanged(false)
   {
     mModel = Model::New();
@@ -523,6 +524,13 @@ struct Controller::Impl
   Length GetNumberOfWhiteSpaces(CharacterIndex index) const;
 
   /**
+   * @brief Retrieve any text previously set.
+   *
+   * @param[out] text A string of UTF-8 characters.
+   */
+  void GetText(std::string& text) const;
+
+  /**
    * @brief Retrieve any text previously set starting from the given @p index.
    *
    * @param[in] index The character index from where to retrieve the text.
@@ -545,42 +553,23 @@ struct Controller::Impl
   }
 
   /**
-   * @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.
-   *
-   * @param[out] numberOfCharacters The number of characters to be updated.
-   */
-  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.
+   * @copydoc Controller::GetLayoutDirection()
    */
-  void ClearFullModelData(OperationsMask operations);
+  Dali::LayoutDirection::Type GetLayoutDirection(Dali::Actor& actor) const;
 
   /**
-   * @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.
+   * @brief Checks text direction.
+   * @return The text direction.
    */
-  void ClearCharacterModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations);
+  Toolkit::DevelText::TextDirection::Type GetTextDirection();
 
   /**
-   * @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.
+   * @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.
    *
-   * @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.
+   * @param[out] numberOfCharacters The number of characters to be updated.
    */
-  void ClearGlyphModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations);
+  void CalculateTextUpdateIndices(Length& numberOfCharacters);
 
   /**
    * @brief Helper to clear the parts of the model specified by the given @p operations and from @p startIndex to @p endIndex.
@@ -620,6 +609,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;
@@ -630,6 +629,21 @@ struct Controller::Impl
   bool SetPrimaryCursorPosition(CharacterIndex index, bool focused);
 
   /**
+   * @copydoc Text::SelectableControlInterface::GetSelectedText()
+   */
+  string GetSelectedText();
+
+  /**
+   * @copydoc Text::EditableControlInterface::CopyText()
+   */
+  string CopyText();
+
+  /**
+   * @copydoc Text::EditableControlInterface::CutText()
+   */
+  string CutText();
+
+  /**
    * @copydoc Text::SelectableControlInterface::SetTextSelectionRange()
    */
   void SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEndf);
@@ -650,6 +664,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.
@@ -765,11 +784,127 @@ struct Controller::Impl
   void ScrollTextToMatchCursor();
 
   /**
-   * @brief Create an actor that renders the text background color
+   * @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();
+
+  /**
+   * @brief Resets a provided vector with actors that marks the position of anchors in markup enabled text
+   *
+   * @param[out] anchorActors the vector of actor (empty collection if no anchors available).
+   */
+  void GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors);
+
+  /**
+   * @brief Return an index of first anchor in the anchor vector whose boundaries includes given character offset
+   *
+   * @param[in] characterOffset A position in text coords.
+   *
+   * @return the 0-based index in anchor vector (-1 if an anchor not found)
+   */
+  int32_t GetAnchorIndex(size_t characterOffset) const;
+
+  /**
+   * @brief Return the geometrical position of an anchor relative to the parent origin point.
+   *
+   * @param[in] anchor An anchor.
+   *
+   * @return The x, y, z coordinates of an anchor.
+   */
+  Vector3 GetAnchorPosition(Anchor anchor) const;
+
+  /**
+   * @brief Return the size of an anchor expresed as a vector containing anchor's width and height.
+   *
+   * @param[in] anchor An anchor.
    *
-   * @return the created actor or an empty handle if no background color needs to be rendered.
+   * @return The width and height of an anchor.
    */
-  Actor CreateBackgroundActor();
+  Vector2 GetAnchorSize(Anchor anchor) const;
+
+  /**
+   * @brief Return the actor representing an anchor.
+   *
+   * @param[in] anchor An anchor.
+   *
+   * @return The actor representing an anchor.
+   */
+  Toolkit::TextAnchor CreateAnchorActor(Anchor anchor);
 
 public:
   /**
@@ -840,8 +975,9 @@ public:
   float mTextFitMinSize;               ///< Minimum Font Size for text fit. Default 10
   float mTextFitMaxSize;               ///< Maximum Font Size for text fit. Default 100
   float mTextFitStepSize;              ///< Step Size for font intervalse. Default 1
-  bool  mTextFitEnabled : 1;           ///< Whether the text's fit is enabled.
   float mFontSizeScale;                ///< Scale value for Font Size. Default 1.0
+  bool  mTextFitEnabled : 1;           ///< Whether the text's fit is enabled.
+  bool  mTextFitChanged : 1;           ///< Whether the text fit property has changed.
   bool  mIsLayoutDirectionChanged : 1; ///< Whether the layout has changed.
 
 private:
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 d649371..312ecb5 100644 (file)
@@ -61,7 +61,7 @@ void Controller::PlaceholderHandler::SetPlaceholderTextElideEnabled(Controller&
   if(controller.mImpl->IsShowingPlaceholderText() ||
      (0u == controller.mImpl->mModel->mLogicalModel->mText.Count()))
   {
-    controller.ShowPlaceholderText();
+    ShowPlaceholderText(*controller.mImpl);
   }
 }
 
@@ -87,7 +87,7 @@ void Controller::PlaceholderHandler::SetPlaceholderText(Controller& controller,
     if(controller.mImpl->IsShowingPlaceholderText() ||
        (0u == controller.mImpl->mModel->mLogicalModel->mText.Count()))
     {
-      controller.ShowPlaceholderText();
+      ShowPlaceholderText(*controller.mImpl);
     }
   }
 }
@@ -443,10 +443,8 @@ void Controller::PlaceholderHandler::GetPlaceholderProperty(Controller& controll
   }
 }
 
-void Controller::PlaceholderHandler::ShowPlaceholderText(Controller& controller)
+void Controller::PlaceholderHandler::ShowPlaceholderText(Controller::Impl& impl)
 {
-  Controller::Impl& impl = *controller.mImpl;
-
   if(impl.IsPlaceholderAvailable())
   {
     EventData*& eventData = impl.mEventData;
index ac2ba1b..9c81eb1 100644 (file)
@@ -55,7 +55,7 @@ struct Controller::PlaceholderHandler
   static const Vector4&     GetPlaceholderTextColor(const Controller& controller);
   static void               SetPlaceholderProperty(Controller& controller, const Property::Map& map);
   static void               GetPlaceholderProperty(Controller& controller, Property::Map& map);
-  static void               ShowPlaceholderText(Controller& controller);
+  static void               ShowPlaceholderText(Controller::Impl& impl);
   static void               CreatePlaceholderFont(Controller& controller);
 };
 
index 4c71934..3013b73 100644 (file)
@@ -24,6 +24,7 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
+#include <dali-toolkit/internal/text/text-controller-event-handler.h>
 #include <dali-toolkit/internal/text/text-controller-impl.h>
 
 namespace
@@ -94,7 +95,7 @@ Size Controller::Relayouter::CalculateLayoutSizeOnRequiredControllerSize(Control
   // Store the actual control's size to restore later.
   const Size actualControlSize = visualModel->mControlSize;
 
-  DoRelayout(controller,
+  DoRelayout(impl,
              requestedControllerSize,
              static_cast<OperationsMask>(onlyOnceOperations |
                                          requestedOperationsMask),
@@ -125,7 +126,7 @@ Vector3 Controller::Relayouter::GetNaturalSize(Controller& controller)
   Vector3 naturalSizeVec3;
 
   // Make sure the model is up-to-date before layouting
-  controller.ProcessModifyEvents();
+  EventHandler::ProcessModifyEvents(controller);
 
   Controller::Impl& impl        = *controller.mImpl;
   ModelPtr&         model       = impl.mModel;
@@ -170,7 +171,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 |
@@ -187,7 +188,7 @@ bool Controller::Relayouter::CheckForTextFit(Controller& controller, float point
   // Make sure the model is up-to-date before layouting
   impl.UpdateModel(onlyOnceOperations);
 
-  DoRelayout(controller,
+  DoRelayout(impl,
              Size(layoutSize.width, MAX_FLOAT),
              static_cast<OperationsMask>(onlyOnceOperations | LAYOUT),
              textSize);
@@ -216,6 +217,7 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const
     float minPointSize   = impl.mTextFitMinSize;
     float maxPointSize   = impl.mTextFitMaxSize;
     float pointInterval  = impl.mTextFitStepSize;
+    float currentFitPointSize = impl.mFontDefaults->mFitPointSize;
 
     model->mElideEnabled = false;
     Vector<float> pointSizeArray;
@@ -255,9 +257,13 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const
     }
 
     model->mElideEnabled              = actualellipsis;
+    if(currentFitPointSize != pointSizeArray[bestSizeIndex])
+    {
+      impl.mTextFitChanged = true;
+    }
     impl.mFontDefaults->mFitPointSize = pointSizeArray[bestSizeIndex];
     impl.mFontDefaults->sizeDefined   = true;
-    controller.ClearFontData();
+    impl.ClearFontData();
   }
 }
 
@@ -266,7 +272,7 @@ float Controller::Relayouter::GetHeightForWidth(Controller& controller, float wi
   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", &controller, width);
 
   // Make sure the model is up-to-date before layouting
-  controller.ProcessModifyEvents();
+  EventHandler::ProcessModifyEvents(controller);
 
   Controller::Impl& impl           = *controller.mImpl;
   ModelPtr&         model          = impl.mModel;
@@ -402,12 +408,12 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll
   }
 
   // Make sure the model is up-to-date before layouting.
-  controller.ProcessModifyEvents();
+  EventHandler::ProcessModifyEvents(controller);
   bool updated = impl.UpdateModel(operationsPending);
 
   // Layout the text.
   Size layoutSize;
-  updated = DoRelayout(controller, size, operationsPending, layoutSize) || updated;
+  updated = DoRelayout(impl, size, operationsPending, layoutSize) || updated;
 
   if(updated)
   {
@@ -431,7 +437,7 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll
   if(!isEditable || !controller.IsMultiLineEnabled())
   {
     // After doing the text layout, the vertical offset to place the actor in the desired position can be calculated.
-    controller.CalculateVerticalOffset(size);
+    CalculateVerticalOffset(controller, size);
   }
 
   if(isEditable)
@@ -459,13 +465,11 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll
   return updateTextType;
 }
 
-bool Controller::Relayouter::DoRelayout(Controller& controller, const Size& size, OperationsMask operationsRequired, Size& layoutSize)
+bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize)
 {
-  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::DoRelayout %p size %f,%f\n", &controller, size.width, size.height);
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayouter::DoRelayout %p size %f,%f\n", &impl, size.width, size.height);
   bool viewUpdated(false);
 
-  Controller::Impl& impl = *controller.mImpl;
-
   // Calculate the operations to be done.
   const OperationsMask operations = static_cast<OperationsMask>(impl.mOperationsPending & operationsRequired);
 
@@ -500,7 +504,7 @@ bool Controller::Relayouter::DoRelayout(Controller& controller, const Size& size
        (lastIndex > charactersToGlyph.Count() && charactersToGlyph.Count() > 0u))
     {
       std::string currentText;
-      controller.GetText(currentText);
+      impl.GetText(currentText);
 
       DALI_LOG_ERROR("Controller::DoRelayout: Attempting to access invalid buffer\n");
       DALI_LOG_ERROR("Current text is: %s\n", currentText.c_str());
@@ -561,7 +565,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();
       }
     }
 
@@ -631,10 +635,10 @@ bool Controller::Relayouter::DoRelayout(Controller& controller, const Size& size
   }
 #if defined(DEBUG_ENABLED)
   std::string currentText;
-  controller.GetText(currentText);
-  DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", &controller, (impl.mIsTextDirectionRTL) ? "true" : "false", currentText.c_str());
+  impl.GetText(currentText);
+  DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::Relayouter::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", &impl, (impl.mIsTextDirectionRTL) ? "true" : "false", currentText.c_str());
 #endif
-  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::DoRelayout, view updated %s\n", (viewUpdated ? "true" : "false"));
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, view updated %s\n", (viewUpdated ? "true" : "false"));
   return viewUpdated;
 }
 
index 1b586b2..decca97 100644 (file)
@@ -85,13 +85,14 @@ struct Controller::Relayouter
   /**
    * @brief Called by the Controller to do certain operations when relayouting.
    *
-   * @param[in] controller A reference to the controller class
+   * @param[in] impl A reference to the controller impl class
    * @param[in] size The size to set
    * @param[in] operationsRequired The operations we need to do
    * @param[in/out] layoutSize The Layout size which can be updated depending on the result of the performed operations
    * @return
    */
-  static bool DoRelayout(Controller& controller, const Size& size, OperationsMask operationsRequired, Size& layoutSize);
+
+  static bool DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize);
 
   /**
    * @brief Called by the Controller to calculate the veritcal offset give the control size.
index 75ea73c..0bcb97f 100644 (file)
@@ -26,6 +26,7 @@
 #include <dali-toolkit/internal/text/character-set-conversion.h>
 #include <dali-toolkit/internal/text/markup-processor.h>
 #include <dali-toolkit/internal/text/text-controller-impl.h>
+#include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
 
 namespace
@@ -55,7 +56,7 @@ void Controller::TextUpdater::SetText(Controller& controller, const std::string&
   ResetText(controller);
 
   // Remove the style.
-  controller.ClearStyleData();
+  impl.ClearStyleData();
 
   CharacterIndex lastCursorIndex = 0u;
 
@@ -141,7 +142,7 @@ void Controller::TextUpdater::SetText(Controller& controller, const std::string&
   }
   else
   {
-    controller.ShowPlaceholderText();
+    PlaceholderHandler::ShowPlaceholderText(impl);
   }
 
   unsigned int oldCursorPos = (nullptr != eventData ? eventData->mPrimaryCursorPosition : 0);
@@ -150,7 +151,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();
 
@@ -405,7 +406,7 @@ void Controller::TextUpdater::InsertText(Controller& controller, const std::stri
      impl.IsPlaceholderAvailable())
   {
     // Show place-holder if empty after removing the pre-edit text
-    controller.ShowPlaceholderText();
+    PlaceholderHandler::ShowPlaceholderText(impl);
     eventData->mUpdateCursorPosition = true;
     impl.ClearPreEditFlag();
   }
index f512940..1070966 100644 (file)
@@ -39,32 +39,64 @@ struct Controller::TextUpdater
   /// @param[in] controller The controller
   static void SetText(Controller& controller, const std::string& text);
 
-  /// @copydoc Text::Contoller::InsertText
-  /// @param[in] controller The controller
+  /**
+   * @brief Called by editable UI controls when key events are received.
+   *
+   * @param[in] controller The controller
+   * @param[in] text The text to insert.
+   * @param[in] type Used to distinguish between regular key events and InputMethodContext events.
+   */
   static void InsertText(Controller& controller, const std::string& text, Controller::InsertType type);
 
-  /// @copydoc Text::Contoller::PasteText
+  /// @copydoc Text::EditableControlInterface::PasteText()
   /// @param[in] controller The controller
   static void PasteText(Controller& controller, const std::string& stringToPaste);
 
-  /// @copydoc Text::Contoller::RemoveText
-  /// @param[in] controller The controller
+  /**
+   * @brief Remove a given number of characters
+   *
+   * When predictve text is used the pre-edit text is removed and inserted again with the new characters.
+   * The UpdateInputStyleType @type parameter if set to DONT_UPDATE_INPUT_STYLE avoids to update the input
+   * style when pre-edit text is removed.
+   *
+   * @param[in] controller The controller
+   * @param[in] cursorOffset Start position from the current cursor position to start deleting characters.
+   * @param[in] numberOfCharacters The number of characters to delete from the cursorOffset.
+   * @param[in] type Whether to update the input style.
+   * @return True if the remove was successful.
+   */
   static bool RemoveText(Controller& controller, int cursorOffset, int numberOfCharacters, UpdateInputStyleType type);
 
-  /// @copydoc Text::Contoller::RemoveSelectedText
-  /// @param[in] controller The controller
+  /**
+   * @brief Checks if text is selected and if so removes it.
+   * @param[in] controller The controller
+   * @return true if text was removed
+   */
   static bool RemoveSelectedText(Controller& controller);
 
-  /// @copydoc Text::Contoller::ResetText
-  /// @param[in] controller The controller
+  /**
+   * @brief Used to remove the text included the placeholder text.
+   * @param[in] controller The controller
+   */
   static void ResetText(Controller& controller);
 
-  /// @copydoc Text::Contoller::InsertTextAnchor
-  /// @param[in] controller The controller
+  /**
+   * @brief Update anchor position from given number of inserted characters.
+   *
+   * @param[in] controller The controller
+   * @param[in] numberOfCharacters The number of inserted characters.
+   * @param[in] previousCursorIndex A cursor position before event occurs.
+   */
   static void InsertTextAnchor(Controller& controller, int numberOfCharacters, CharacterIndex previousCursorIndex);
 
-  /// @copydoc Text::Contoller::RemoveTextAnchor
-  /// @param[in] controller The controller
+  /**
+   * @brief Update anchor position from given number of removed characters.
+   *
+   * @param[in] controller The controller
+   * @param[in] cursorOffset Start position from the current cursor position to start deleting characters.
+   * @param[in] numberOfCharacters The number of removed characters.
+   * @param[in] previousCursorIndex A cursor position before event occurs.
+   */
   static void RemoveTextAnchor(Controller& controller, int cursorOffset, int numberOfCharacters, CharacterIndex previousCursorIndex);
 };
 
index 6e4d361..757d54a 100644 (file)
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/text/text-enumerations-devel.h>
+#include <dali-toolkit/internal/text/text-controller-background-actor.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-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>
@@ -42,8 +44,6 @@ namespace
 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
 #endif
 
-constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
-
 const std::string EMPTY_STRING("");
 
 template<typename Type>
@@ -137,7 +137,7 @@ void Controller::SetGlyphType(TextAbstraction::GlyphType glyphType)
   mImpl->mMetrics->SetGlyphType(glyphType);
 
   // Clear the font-specific data
-  ClearFontData();
+  mImpl->ClearFontData();
 
   mImpl->RequestRelayout();
 }
@@ -169,39 +169,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 +244,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 +254,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 |
-