2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/controller/text-controller-impl.h>
22 #include <dali/integration-api/adaptor-framework/scene-holder.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/actors/layer.h>
25 #include <dali/public-api/rendering/renderer.h>
29 #include <dali-toolkit/internal/text/character-set-conversion.h>
30 #include <dali-toolkit/internal/text/controller/text-controller-impl-data-clearer.h>
31 #include <dali-toolkit/internal/text/controller/text-controller-impl-event-handler.h>
32 #include <dali-toolkit/internal/text/controller/text-controller-impl-model-updater.h>
33 #include <dali-toolkit/internal/text/controller/text-controller-placeholder-handler.h>
34 #include <dali-toolkit/internal/text/controller/text-controller-relayouter.h>
35 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
36 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
37 #include <dali-toolkit/internal/text/text-control-interface.h>
38 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
39 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
40 #include <dali-toolkit/internal/text/text-run-container.h>
41 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
42 #include <dali-toolkit/internal/text/underlined-glyph-run.h>
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
52 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
54 const char* EMPTY_STRING = "";
55 const char* MIME_TYPE_TEXT_PLAIN = "text/plain;charset=utf-8";
56 const char* MIME_TYPE_HTML = "application/xhtml+xml";
60 namespace Dali::Toolkit::Text
64 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
66 // Sets the default text's color.
67 inputStyle.textColor = textColor;
68 inputStyle.isDefaultColor = true;
70 inputStyle.familyName.clear();
71 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
72 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
73 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
74 inputStyle.size = 0.f;
76 inputStyle.lineSpacing = 0.f;
78 inputStyle.underlineProperties.clear();
79 inputStyle.shadowProperties.clear();
80 inputStyle.embossProperties.clear();
81 inputStyle.outlineProperties.clear();
83 inputStyle.isFamilyDefined = false;
84 inputStyle.isWeightDefined = false;
85 inputStyle.isWidthDefined = false;
86 inputStyle.isSlantDefined = false;
87 inputStyle.isSizeDefined = false;
89 inputStyle.isLineSpacingDefined = false;
91 inputStyle.isUnderlineDefined = false;
92 inputStyle.isShadowDefined = false;
93 inputStyle.isEmbossDefined = false;
94 inputStyle.isOutlineDefined = false;
96 // Sets the default font's family name, weight, width, slant and size.
99 if(fontDefaults->familyDefined)
101 inputStyle.familyName = fontDefaults->mFontDescription.family;
102 inputStyle.isFamilyDefined = true;
105 if(fontDefaults->weightDefined)
107 inputStyle.weight = fontDefaults->mFontDescription.weight;
108 inputStyle.isWeightDefined = true;
111 if(fontDefaults->widthDefined)
113 inputStyle.width = fontDefaults->mFontDescription.width;
114 inputStyle.isWidthDefined = true;
117 if(fontDefaults->slantDefined)
119 inputStyle.slant = fontDefaults->mFontDescription.slant;
120 inputStyle.isSlantDefined = true;
123 if(fontDefaults->sizeDefined)
125 inputStyle.size = fontDefaults->mDefaultPointSize;
126 inputStyle.isSizeDefined = true;
131 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
133 EventData* eventData = impl.mEventData;
135 if(nullptr == eventData)
137 // Nothing to do if there is no text input.
141 DecoratorPtr& decorator = eventData->mDecorator;
144 // Nothing to do if there is no decorator.
148 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
150 if(eventData->mState != newState)
152 eventData->mPreviousState = eventData->mState;
153 eventData->mState = newState;
155 switch(eventData->mState)
157 case EventData::INACTIVE:
159 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
160 decorator->StopCursorBlink();
161 decorator->SetHandleActive(GRAB_HANDLE, false);
162 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
163 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
164 decorator->SetHighlightActive(false);
165 decorator->SetPopupActive(false);
166 eventData->mDecoratorUpdated = true;
170 case EventData::INTERRUPTED:
172 decorator->SetHandleActive(GRAB_HANDLE, false);
173 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
174 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
175 decorator->SetHighlightActive(false);
176 decorator->SetPopupActive(false);
177 eventData->mDecoratorUpdated = true;
181 case EventData::SELECTING:
183 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
184 decorator->StopCursorBlink();
185 decorator->SetHandleActive(GRAB_HANDLE, false);
186 if(eventData->mGrabHandleEnabled)
188 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
189 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
191 decorator->SetHighlightActive(true);
192 if(eventData->mGrabHandlePopupEnabled)
194 impl.SetPopupButtons();
195 decorator->SetPopupActive(true);
197 eventData->mDecoratorUpdated = true;
201 case EventData::EDITING:
203 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
204 if(eventData->mCursorBlinkEnabled)
206 decorator->StartCursorBlink();
208 // Grab handle is not shown until a tap is received whilst EDITING
209 decorator->SetHandleActive(GRAB_HANDLE, false);
210 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
211 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
212 decorator->SetHighlightActive(false);
213 if(eventData->mGrabHandlePopupEnabled)
215 decorator->SetPopupActive(false);
217 eventData->mDecoratorUpdated = true;
220 case EventData::EDITING_WITH_POPUP:
222 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
224 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
225 if(eventData->mCursorBlinkEnabled)
227 decorator->StartCursorBlink();
229 if(eventData->mSelectionEnabled)
231 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
232 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
233 decorator->SetHighlightActive(false);
235 else if(eventData->mGrabHandleEnabled)
237 decorator->SetHandleActive(GRAB_HANDLE, true);
239 if(eventData->mGrabHandlePopupEnabled)
241 impl.SetPopupButtons();
242 decorator->SetPopupActive(true);
244 eventData->mDecoratorUpdated = true;
247 case EventData::EDITING_WITH_GRAB_HANDLE:
249 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
251 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
252 if(eventData->mCursorBlinkEnabled)
254 decorator->StartCursorBlink();
256 // Grab handle is not shown until a tap is received whilst EDITING
257 if(eventData->mGrabHandleEnabled)
259 decorator->SetHandleActive(GRAB_HANDLE, true);
261 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
262 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
263 decorator->SetHighlightActive(false);
264 if(eventData->mGrabHandlePopupEnabled)
266 decorator->SetPopupActive(false);
268 eventData->mDecoratorUpdated = true;
272 case EventData::SELECTION_HANDLE_PANNING:
274 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
275 decorator->StopCursorBlink();
276 decorator->SetHandleActive(GRAB_HANDLE, false);
277 if(eventData->mGrabHandleEnabled)
279 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
280 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
282 decorator->SetHighlightActive(true);
283 if(eventData->mGrabHandlePopupEnabled)
285 decorator->SetPopupActive(false);
287 eventData->mDecoratorUpdated = true;
291 case EventData::GRAB_HANDLE_PANNING:
293 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
295 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
296 if(eventData->mCursorBlinkEnabled)
298 decorator->StartCursorBlink();
300 if(eventData->mGrabHandleEnabled)
302 decorator->SetHandleActive(GRAB_HANDLE, true);
304 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
305 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
306 decorator->SetHighlightActive(false);
307 if(eventData->mGrabHandlePopupEnabled)
309 decorator->SetPopupActive(false);
311 eventData->mDecoratorUpdated = true;
315 case EventData::EDITING_WITH_PASTE_POPUP:
317 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
319 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
320 if(eventData->mCursorBlinkEnabled)
322 decorator->StartCursorBlink();
325 if(eventData->mGrabHandleEnabled)
327 decorator->SetHandleActive(GRAB_HANDLE, true);
329 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
330 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
331 decorator->SetHighlightActive(false);
333 if(eventData->mGrabHandlePopupEnabled)
335 impl.SetPopupButtons();
336 decorator->SetPopupActive(true);
338 eventData->mDecoratorUpdated = true;
342 case EventData::TEXT_PANNING:
344 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
345 decorator->StopCursorBlink();
346 decorator->SetHandleActive(GRAB_HANDLE, false);
347 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
348 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
350 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
351 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
352 decorator->SetHighlightActive(true);
355 if(eventData->mGrabHandlePopupEnabled)
357 decorator->SetPopupActive(false);
360 eventData->mDecoratorUpdated = true;
367 void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment)
369 EventData* eventData = impl.mEventData;
371 // Set the flag to redo the alignment operation
372 impl.mOperationsPending = static_cast<Controller::OperationsMask>(impl.mOperationsPending | Controller::OperationsMask::ALIGN);
376 // Note: mUpdateAlignment is currently only needed for horizontal alignment
377 eventData->mUpdateAlignment = needFullAlignment;
379 // Update the cursor if it's in editing mode
380 if(EventData::IsEditingState(eventData->mState))
382 impl.ChangeState(EventData::EDITING);
383 eventData->mUpdateCursorPosition = true;
388 } // unnamed Namespace
390 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
391 : mDecorator(decorator),
392 mInputMethodContext(inputMethodContext),
393 mPlaceholderFont(nullptr),
394 mPlaceholderTextActive(),
395 mPlaceholderTextInactive(),
396 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
398 mInputStyleChangedQueue(),
399 mPreviousState(INACTIVE),
401 mPrimaryCursorPosition(0u),
402 mLeftSelectionPosition(0u),
403 mRightSelectionPosition(0u),
404 mPreEditStartPosition(0u),
406 mCursorHookPositionX(0.f),
407 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
408 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
409 mIsShowingPlaceholderText(false),
411 mDecoratorUpdated(false),
412 mCursorBlinkEnabled(true),
413 mGrabHandleEnabled(true),
414 mGrabHandlePopupEnabled(true),
415 mSelectionEnabled(true),
416 mUpdateCursorHookPosition(false),
417 mUpdateCursorPosition(false),
418 mUpdateGrabHandlePosition(false),
419 mUpdateLeftSelectionPosition(false),
420 mUpdateRightSelectionPosition(false),
421 mIsLeftHandleSelected(false),
422 mIsRightHandleSelected(false),
423 mUpdateHighlightBox(false),
424 mScrollAfterUpdatePosition(false),
425 mScrollAfterDelete(false),
426 mAllTextSelected(false),
427 mUpdateInputStyle(false),
428 mPasswordInput(false),
429 mCheckScrollAmount(false),
430 mIsPlaceholderPixelSize(false),
431 mIsPlaceholderElideEnabled(false),
432 mPlaceholderEllipsisFlag(false),
433 mShiftSelectionFlag(true),
434 mUpdateAlignment(false),
435 mEditingEnabled(true)
439 bool Controller::Impl::ProcessInputEvents()
441 return ControllerImplEventHandler::ProcessInputEvents(*this);
444 void Controller::Impl::SetAnchorColor(const Vector4& color)
446 mAnchorColor = color;
450 const Vector4& Controller::Impl::GetAnchorColor() const
455 void Controller::Impl::SetAnchorClickedColor(const Vector4& color)
457 mAnchorClickedColor = color;
461 const Vector4& Controller::Impl::GetAnchorClickedColor() const
463 return mAnchorClickedColor;
466 void Controller::Impl::UpdateAnchorColor()
468 if(!mAnchorControlInterface ||
469 !mMarkupProcessorEnabled ||
470 !mModel->mLogicalModel->mAnchors.Count() ||
471 !IsShowingRealText())
476 bool updateNeeded = false;
478 // The anchor color & clicked color needs to be updated with the property's color.
479 for(auto& anchor : mModel->mLogicalModel->mAnchors)
481 if(!anchor.isMarkupColorSet && !anchor.isClicked)
483 if(mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
485 ColorRun& colorRun = *(mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
486 colorRun.color = mAnchorColor;
489 if(mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
491 UnderlinedCharacterRun& underlineRun = *(mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
492 underlineRun.properties.color = mAnchorColor;
496 else if(!anchor.isMarkupClickedColorSet && anchor.isClicked)
498 if(mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
500 ColorRun& colorRun = *(mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
501 colorRun.color = mAnchorClickedColor;
504 if(mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
506 UnderlinedCharacterRun& underlineRun = *(mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
507 underlineRun.properties.color = mAnchorClickedColor;
516 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
521 void Controller::Impl::NotifyInputMethodContext()
523 if(mEventData && mEventData->mInputMethodContext)
525 CharacterIndex cursorPosition = GetLogicalCursorPosition();
526 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
527 mEventData->mInputMethodContext.NotifyCursorPosition();
531 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
533 if(mEventData && mEventData->mInputMethodContext)
535 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
536 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
540 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
542 CharacterIndex cursorPosition = 0u;
546 if((EventData::SELECTING == mEventData->mState) ||
547 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
549 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
553 cursorPosition = mEventData->mPrimaryCursorPosition;
557 return cursorPosition;
560 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
562 Length numberOfWhiteSpaces = 0u;
564 // Get the buffer to the text.
565 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
567 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
568 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
570 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
576 return numberOfWhiteSpaces;
579 void Controller::Impl::GetText(std::string& text) const
581 if(!IsShowingPlaceholderText())
583 // Retrieves the text string.
588 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
592 Length Controller::Impl::GetNumberOfCharacters() const
594 if(!IsShowingPlaceholderText())
596 return mModel->GetNumberOfCharacters();
600 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetNumberOfCharacters %p empty (but showing placeholder)\n", this);
605 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
607 // Get the total number of characters.
608 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
610 // Retrieve the text.
611 if(0u != numberOfCharacters)
613 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
617 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
619 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
620 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
622 Integration::SceneHolder sceneHolder = Integration::SceneHolder::Get(actor);
623 return static_cast<Dali::LayoutDirection::Type>(sceneHolder ? sceneHolder.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
627 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
631 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
633 if(mUpdateTextDirection)
635 // Operations that can be done only once until the text changes.
636 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
644 // Set the update info to relayout the whole text.
645 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
646 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
648 // Make sure the model is up-to-date before layouting
649 UpdateModel(onlyOnceOperations);
652 Relayouter::DoRelayout(*this,
653 Size(MAX_FLOAT, MAX_FLOAT),
654 static_cast<OperationsMask>(onlyOnceOperations |
655 LAYOUT | REORDER | UPDATE_DIRECTION),
656 naturalSize.GetVectorXY());
658 // Do not do again the only once operations.
659 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
661 // Clear the update info. This info will be set the next time the text is updated.
662 mTextUpdateInfo.Clear();
664 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
665 mTextUpdateInfo.mFullRelayoutNeeded = true;
667 mUpdateTextDirection = false;
670 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
673 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
675 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
676 mTextUpdateInfo.mStartGlyphIndex = 0u;
677 mTextUpdateInfo.mStartLineIndex = 0u;
678 numberOfCharacters = 0u;
680 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
681 if(0u == numberOfParagraphs)
683 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
684 numberOfCharacters = 0u;
686 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
688 // Nothing else to do if there are no paragraphs.
692 // Find the paragraphs to be updated.
693 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
694 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
696 // Text is being added at the end of the current text.
697 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
699 // Text is being added in a new paragraph after the last character of the text.
700 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
701 numberOfCharacters = 0u;
702 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
704 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
705 mTextUpdateInfo.mStartLineIndex = (mModel->mVisualModel->mLines.Count() > 0u) ? mModel->mVisualModel->mLines.Count() - 1u : 0u;
707 // Nothing else to do;
711 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
715 Length numberOfCharactersToUpdate = 0u;
716 if(mTextUpdateInfo.mFullRelayoutNeeded)
718 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
722 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
724 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
725 numberOfCharactersToUpdate,
726 paragraphsToBeUpdated);
729 if(0u != paragraphsToBeUpdated.Count())
731 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
732 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
733 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
735 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
736 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
738 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
739 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
740 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
741 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
743 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
744 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
746 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
750 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
754 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
755 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
758 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
760 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
763 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
765 return ControllerImplModelUpdater::Update(*this, operationsRequired);
768 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
770 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
773 float Controller::Impl::GetDefaultFontLineHeight()
775 FontId defaultFontId = 0u;
776 if(nullptr == mFontDefaults)
778 TextAbstraction::FontDescription fontDescription;
779 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
783 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
786 Text::FontMetrics fontMetrics;
787 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
789 return (fontMetrics.ascender - fontMetrics.descender);
792 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
794 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
796 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
798 RelayoutAllCharacters();
804 bool Controller::Impl::SetDefaultLineSize(float lineSize)
806 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
808 mLayoutEngine.SetDefaultLineSize(lineSize);
810 RelayoutAllCharacters();
816 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
818 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
820 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
822 RelayoutAllCharacters();
828 float Controller::Impl::GetRelativeLineSize()
830 return mLayoutEngine.GetRelativeLineSize();
833 string Controller::Impl::GetSelectedText()
836 if(EventData::SELECTING == mEventData->mState)
838 RetrieveSelection(text, false);
843 string Controller::Impl::CopyText()
846 RetrieveSelection(text, false);
847 SendSelectionToClipboard(false); // Text not modified
849 mEventData->mUpdateCursorPosition = true;
851 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
856 string Controller::Impl::CutText()
859 RetrieveSelection(text, false);
866 SendSelectionToClipboard(true); // Synchronous call to modify text
867 mOperationsPending = ALL_OPERATIONS;
869 if((0u != mModel->mLogicalModel->mText.Count()) ||
870 !IsPlaceholderAvailable())
872 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
876 PlaceholderHandler::ShowPlaceholderText(*this);
879 mEventData->mUpdateCursorPosition = true;
880 mEventData->mScrollAfterDelete = true;
884 if(nullptr != mEditableControlInterface)
886 mEditableControlInterface->TextChanged(true);
891 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
893 if(nullptr == mEventData)
895 // Nothing to do if there is no text.
899 if(mEventData->mSelectionEnabled && (pStart || pEnd))
901 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
902 uint32_t oldStart = mEventData->mLeftSelectionPosition;
903 uint32_t oldEnd = mEventData->mRightSelectionPosition;
907 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
911 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
914 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
916 ChangeState(EventData::EDITING);
917 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
918 mEventData->mUpdateCursorPosition = true;
922 ChangeState(EventData::SELECTING);
923 mEventData->mUpdateHighlightBox = true;
924 mEventData->mUpdateLeftSelectionPosition = true;
925 mEventData->mUpdateRightSelectionPosition = true;
928 if(mSelectableControlInterface != nullptr)
930 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
935 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
937 if(nullptr == mEventData)
941 return mEventData->mPrimaryCursorPosition;
944 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
946 if(nullptr == mEventData)
948 // Nothing to do if there is no text.
952 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
954 // Nothing for same cursor position.
958 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
959 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
960 mEventData->mPrimaryCursorPosition = std::min(index, length);
961 // If there is no focus, only the value is updated.
964 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
965 uint32_t oldStart = mEventData->mLeftSelectionPosition;
966 uint32_t oldEnd = mEventData->mRightSelectionPosition;
967 ChangeState(EventData::EDITING);
968 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
969 mEventData->mUpdateCursorPosition = true;
971 if(mSelectableControlInterface != nullptr && wasInSelectingState)
973 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
976 ScrollTextToMatchCursor();
979 if(nullptr != mEditableControlInterface)
981 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
987 Uint32Pair Controller::Impl::GetTextSelectionRange() const
993 range.first = mEventData->mLeftSelectionPosition;
994 range.second = mEventData->mRightSelectionPosition;
1000 bool Controller::Impl::IsEditable() const
1002 return mEventData && mEventData->mEditingEnabled;
1005 void Controller::Impl::SetEditable(bool editable)
1009 mEventData->mEditingEnabled = editable;
1011 if(mEventData->mDecorator)
1013 bool decoratorEditable = editable && mIsUserInteractionEnabled;
1014 mEventData->mDecorator->SetEditable(decoratorEditable);
1015 mEventData->mDecoratorUpdated = true;
1021 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
1023 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
1025 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
1027 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
1028 mFontDefaults->mFontDescription.family = newDefaultFont;
1036 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
1038 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
1040 // Nothing to select if handles are in the same place.
1041 selectedText.clear();
1045 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1047 //Get start and end position of selection
1048 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1049 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
1051 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1052 const Length numberOfCharacters = utf32Characters.Count();
1054 // Validate the start and end selection points
1055 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
1057 //Get text as a UTF8 string
1058 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
1060 if(deleteAfterRetrieval) // Only delete text if copied successfully
1062 // Keep a copy of the current input style.
1063 InputStyle currentInputStyle;
1064 currentInputStyle.Copy(mEventData->mInputStyle);
1066 // Set as input style the style of the first deleted character.
1067 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
1069 // Compare if the input style has changed.
1070 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
1072 if(hasInputStyleChanged)
1074 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
1075 // Queue the input style changed signal.
1076 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
1079 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1081 // Mark the paragraphs to be updated.
1082 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1084 mTextUpdateInfo.mCharacterIndex = 0;
1085 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1086 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1087 mTextUpdateInfo.mClearAll = true;
1091 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1092 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1095 // Delete text between handles
1096 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1097 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1098 utf32Characters.Erase(first, last);
1100 // Will show the cursor at the first character of the selection.
1101 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1105 // Will show the cursor at the last character of the selection.
1106 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1109 mEventData->mDecoratorUpdated = true;
1113 void Controller::Impl::SetSelection(int start, int end)
1115 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1116 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1118 mEventData->mLeftSelectionPosition = start;
1119 mEventData->mRightSelectionPosition = end;
1120 mEventData->mUpdateCursorPosition = true;
1122 if(mSelectableControlInterface != nullptr)
1124 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1128 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1130 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1133 void Controller::Impl::ShowClipboard()
1135 if(EnsureClipboardCreated())
1137 mClipboard.ShowClipboard();
1141 void Controller::Impl::HideClipboard()
1143 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1145 mClipboard.HideClipboard();
1149 void Controller::Impl::SetClipboardHideEnable(bool enable)
1151 mClipboardHideEnabled = enable;
1154 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1156 if(EnsureClipboardCreated())
1158 Dali::Clipboard::ClipData data(MIME_TYPE_TEXT_PLAIN, source.c_str());
1159 return mClipboard.SetData(data); // Send clipboard data to clipboard.
1165 bool Controller::Impl::IsClipboardEmpty()
1167 bool result(Clipboard::IsAvailable() && EnsureClipboardCreated() && (mClipboard.HasType(MIME_TYPE_TEXT_PLAIN) || mClipboard.HasType(MIME_TYPE_HTML)));
1171 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1173 std::string selectedText;
1174 RetrieveSelection(selectedText, deleteAfterSending);
1175 CopyStringToClipboard(selectedText);
1176 ChangeState(EventData::EDITING);
1179 void Controller::Impl::RepositionSelectionHandles()
1181 SelectionHandleController::Reposition(*this);
1183 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1185 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1188 void Controller::Impl::SetPopupButtons()
1191 * Sets the Popup buttons to be shown depending on State.
1193 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1195 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1198 bool isEditable = IsEditable();
1199 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1201 if(EventData::SELECTING == mEventData->mState)
1203 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1206 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1209 if(!IsClipboardEmpty())
1213 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1215 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1218 if(!mEventData->mAllTextSelected)
1220 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1223 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1225 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1227 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1230 if(!IsClipboardEmpty())
1234 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1236 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1239 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1241 if(!IsClipboardEmpty())
1245 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1247 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1251 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1254 void Controller::Impl::ChangeState(EventData::State newState)
1256 ChangeTextControllerState(*this, newState);
1259 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1260 CursorInfo& cursorInfo)
1262 if(!IsShowingRealText())
1264 // Do not want to use the place-holder text to set the cursor position.
1266 // Use the line's height of the font's family set to set the cursor's size.
1267 // If there is no font's family set, use the default font.
1268 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1270 cursorInfo.lineOffset = 0.f;
1271 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1272 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1275 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1277 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1280 switch(mModel->mHorizontalAlignment)
1282 case Text::HorizontalAlignment::BEGIN:
1286 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1290 cursorInfo.primaryPosition.x = 0.f;
1294 case Text::HorizontalAlignment::CENTER:
1296 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1299 case Text::HorizontalAlignment::END:
1303 cursorInfo.primaryPosition.x = 0.f;
1307 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1313 // Nothing else to do.
1317 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1318 GetCursorPositionParameters parameters;
1319 parameters.visualModel = mModel->mVisualModel;
1320 parameters.logicalModel = mModel->mLogicalModel;
1321 parameters.metrics = mMetrics;
1322 parameters.logical = logical;
1323 parameters.isMultiline = isMultiLine;
1325 float defaultFontLineHeight = GetDefaultFontLineHeight();
1327 Text::GetCursorPosition(parameters,
1328 defaultFontLineHeight,
1331 // Adds Outline offset.
1332 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1333 cursorInfo.primaryPosition.x += outlineWidth;
1334 cursorInfo.primaryPosition.y += outlineWidth;
1335 cursorInfo.secondaryPosition.x += outlineWidth;
1336 cursorInfo.secondaryPosition.y += outlineWidth;
1340 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1342 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1343 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1345 if(0.f > cursorInfo.primaryPosition.x)
1347 cursorInfo.primaryPosition.x = 0.f;
1350 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1351 if(cursorInfo.primaryPosition.x > edgeWidth)
1353 cursorInfo.primaryPosition.x = edgeWidth;
1358 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1360 if(nullptr == mEventData)
1362 // Nothing to do if there is no text input.
1366 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1368 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1369 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1371 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1372 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1374 if(numberOfCharacters > 1u)
1376 const Script script = mModel->mLogicalModel->GetScript(index);
1377 if(HasLigatureMustBreak(script))
1379 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1380 numberOfCharacters = 1u;
1385 while(0u == numberOfCharacters)
1388 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1392 if(index < mEventData->mPrimaryCursorPosition)
1394 cursorIndex = cursorIndex < numberOfCharacters ? 0u : cursorIndex - numberOfCharacters;
1398 Length textLength = mModel->mVisualModel->mCharactersToGlyph.Count();
1399 cursorIndex = cursorIndex + numberOfCharacters > textLength ? textLength : cursorIndex + numberOfCharacters;
1402 // Will update the cursor hook position.
1403 mEventData->mUpdateCursorHookPosition = true;
1408 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1410 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1411 if(nullptr == mEventData)
1413 // Nothing to do if there is no text input.
1414 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1418 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1420 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1422 // Sets the cursor position.
1423 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1426 cursorInfo.primaryCursorHeight,
1427 cursorInfo.lineHeight);
1428 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1430 if(mEventData->mUpdateGrabHandlePosition)
1432 // Sets the grab handle position.
1433 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1435 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1436 cursorInfo.lineHeight);
1439 if(cursorInfo.isSecondaryCursor)
1441 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1442 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1443 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1444 cursorInfo.secondaryCursorHeight,
1445 cursorInfo.lineHeight);
1446 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1449 // Set which cursors are active according the state.
1450 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1452 if(cursorInfo.isSecondaryCursor)
1454 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1458 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1463 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1466 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1469 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1470 const CursorInfo& cursorInfo)
1472 SelectionHandleController::Update(*this, handleType, cursorInfo);
1475 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1477 // Clamp between -space & -alignment offset.
1479 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1481 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1482 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1483 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1485 mEventData->mDecoratorUpdated = true;
1489 mModel->mScrollPosition.x = 0.f;
1493 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1495 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1497 // Nothing to do if the text is single line.
1501 // Clamp between -space & 0.
1502 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1504 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1505 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1506 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1508 mEventData->mDecoratorUpdated = true;
1512 mModel->mScrollPosition.y = 0.f;
1516 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1518 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1520 // position is in actor's coords.
1521 const float positionEndX = position.x + cursorWidth;
1522 const float positionEndY = position.y + lineHeight;
1524 // Transform the position to decorator coords.
1525 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1526 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1528 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1529 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1531 if(decoratorPositionBeginX < 0.f)
1533 mModel->mScrollPosition.x = -position.x;
1535 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1537 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1540 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1542 if(decoratorPositionBeginY < 0.f)
1544 mModel->mScrollPosition.y = -position.y;
1546 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1548 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1550 else if(mModel->mLogicalModel->mText.Count() == 0u)
1552 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1557 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1559 // Get the current cursor position in decorator coords.
1560 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1562 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1564 // Calculate the offset to match the cursor position before the character was deleted.
1565 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1567 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1568 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1570 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1571 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1574 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1575 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1577 // Makes the new cursor position visible if needed.
1578 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1581 void Controller::Impl::ScrollTextToMatchCursor()
1583 CursorInfo cursorInfo;
1584 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1585 ScrollTextToMatchCursor(cursorInfo);
1588 void Controller::Impl::RequestRelayout()
1590 if(nullptr != mControlInterface)
1592 mControlInterface->RequestTextRelayout();
1596 void Controller::Impl::RelayoutAllCharacters()
1598 // relayout all characters
1599 mTextUpdateInfo.mCharacterIndex = 0;
1600 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1601 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1602 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1604 mTextUpdateInfo.mFullRelayoutNeeded = true;
1606 // Need to recalculate natural size
1607 mRecalculateNaturalSize = true;
1610 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1612 ChangeState(EventData::EDITING);
1618 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1620 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1623 void Controller::Impl::ProcessInputStyleChangedSignals()
1627 if(mEditableControlInterface)
1629 // Emit the input style changed signal for each mask
1630 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1631 mEventData->mInputStyleChangedQueue.end(),
1632 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1635 mEventData->mInputStyleChangedQueue.Clear();
1639 void Controller::Impl::ScrollBy(Vector2 scroll)
1641 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1643 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1644 const Vector2 currentScroll = mModel->mScrollPosition;
1646 scroll.x = -scroll.x;
1647 scroll.y = -scroll.y;
1649 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1651 mModel->mScrollPosition.x += scroll.x;
1652 ClampHorizontalScroll(layoutSize);
1655 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1657 mModel->mScrollPosition.y += scroll.y;
1658 ClampVerticalScroll(layoutSize);
1661 if(mModel->mScrollPosition != currentScroll)
1663 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1669 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1671 bool isScrollable = false;
1674 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1675 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1676 if(isHorizontalScrollEnabled || isVerticalScrollEnabled)
1678 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1679 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1680 const Vector2& scrollPosition = mModel->mScrollPosition;
1682 if(isHorizontalScrollEnabled)
1684 const float displacementX = displacement.x;
1685 const float positionX = scrollPosition.x + displacementX;
1686 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1688 isScrollable = true;
1692 if(isVerticalScrollEnabled)
1694 const float displacementY = displacement.y;
1695 const float positionY = scrollPosition.y + displacementY;
1696 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1698 isScrollable = true;
1703 return isScrollable;
1706 float Controller::Impl::GetHorizontalScrollPosition()
1708 // Scroll values are negative internally so we convert them to positive numbers
1709 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1712 float Controller::Impl::GetVerticalScrollPosition()
1714 // Scroll values are negative internally so we convert them to positive numbers
1715 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1718 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1721 return Vector3(10.f, 10.f, 10.f);
1724 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1727 return Vector2(10.f, 10.f);
1730 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1732 auto actor = Toolkit::TextAnchor::New();
1733 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1734 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1735 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1736 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1737 const Vector2 anchorSize = GetAnchorSize(anchor);
1738 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1739 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1740 actor.SetProperty(Actor::Property::NAME, anchorText);
1741 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1742 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1743 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1747 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1749 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1750 we need to create and destroy potentially many actors. Some optimization can be considered here.
1751 Maybe a "dirty" flag in mLogicalModel? */
1752 anchorActors.clear();
1753 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1755 auto actor = CreateAnchorActor(anchor);
1756 anchorActors.push_back(actor);
1760 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1762 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1764 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1769 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1772 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1774 //Underlined character runs for markup-processor
1775 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1776 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1777 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1779 if(shouldClearPreUnderlineRuns)
1781 mModel->mVisualModel->mUnderlineRuns.Clear();
1784 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1786 CharacterIndex characterIndex = it->characterRun.characterIndex;
1787 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1789 if(numberOfCharacters == 0)
1794 // Create one run for all glyphs of all run's characters that has same properties
1795 // This enhance performance and reduce the needed memory to store glyphs-runs
1796 UnderlinedGlyphRun underlineGlyphRun;
1797 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1798 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1799 //Copy properties (attributes)
1800 underlineGlyphRun.properties = it->properties;
1802 for(Length index = 1u; index < numberOfCharacters; index++)
1804 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1807 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1810 // Reset flag. The updates have been applied from logical to visual.
1811 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1814 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1816 //Strikethrough character runs from markup-processor
1817 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1818 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1819 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1821 mModel->mVisualModel->mStrikethroughRuns.Clear();
1823 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1825 CharacterIndex characterIndex = it->characterRun.characterIndex;
1826 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1828 if(numberOfCharacters == 0)
1833 StrikethroughGlyphRun strikethroughGlyphRun;
1834 strikethroughGlyphRun.properties = it->properties;
1835 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1836 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1838 for(Length index = 1u; index < numberOfCharacters; index++)
1840 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1843 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1846 // Reset flag. The updates have been applied from logical to visual.
1847 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1850 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1852 //CharacterSpacing character runs from markup-processor
1853 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1854 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1855 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1857 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1859 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1861 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1862 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1864 if(numberOfCharacters == 0)
1869 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1870 characterSpacingGlyphRun.value = it->value;
1871 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1872 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1874 for(Length index = 1u; index < numberOfCharacters; index++)
1876 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1879 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1881 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1884 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1886 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1888 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1891 UPDATE_LAYOUT_SIZE |
1896 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1897 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1901 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1904 mIsAutoScrollEnabled = enable;
1909 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1910 mIsAutoScrollEnabled = false;
1914 void Controller::Impl::SetEnableCursorBlink(bool enable)
1916 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1920 mEventData->mCursorBlinkEnabled = enable;
1922 if(!enable && mEventData->mDecorator)
1924 mEventData->mDecorator->StopCursorBlink();
1929 void Controller::Impl::SetMultiLineEnabled(bool enable)
1931 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1933 if(layout != mLayoutEngine.GetLayout())
1935 // Set the layout type.
1936 mLayoutEngine.SetLayout(layout);
1938 // Set the flags to redo the layout operations
1939 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1940 UPDATE_LAYOUT_SIZE |
1944 mTextUpdateInfo.mFullRelayoutNeeded = true;
1945 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1947 // Need to recalculate natural size
1948 mRecalculateNaturalSize = true;
1954 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1956 if(alignment != mModel->mHorizontalAlignment)
1958 // Set the alignment.
1959 mModel->mHorizontalAlignment = alignment;
1960 UpdateCursorPositionForAlignment(*this, true);
1965 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1967 if(alignment != mModel->mVerticalAlignment)
1969 // Set the alignment.
1970 mModel->mVerticalAlignment = alignment;
1971 UpdateCursorPositionForAlignment(*this, false);
1976 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1978 if(lineWrapMode != mModel->mLineWrapMode)
1980 // Update Text layout for applying wrap mode
1981 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1984 UPDATE_LAYOUT_SIZE |
1987 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1988 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1990 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1993 // Set the text wrap mode.
1994 mModel->mLineWrapMode = lineWrapMode;
1996 mTextUpdateInfo.mCharacterIndex = 0u;
1997 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1998 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
2005 void Controller::Impl::SetDefaultColor(const Vector4& color)
2009 if(!IsShowingPlaceholderText())
2011 mModel->mVisualModel->SetTextColor(color);
2012 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
2017 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
2019 mIsUserInteractionEnabled = enabled;
2021 if(mEventData && mEventData->mDecorator)
2023 bool editable = mEventData->mEditingEnabled && enabled;
2024 mEventData->mDecorator->SetEditable(editable);
2025 mEventData->mDecoratorUpdated = true;
2030 void Controller::Impl::ClearFontData()
2034 mFontDefaults->mFontId = 0u; // Remove old font ID
2037 // Set flags to update the model.
2038 mTextUpdateInfo.mCharacterIndex = 0u;
2039 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
2040 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
2042 mTextUpdateInfo.mClearAll = true;
2043 mTextUpdateInfo.mFullRelayoutNeeded = true;
2044 mRecalculateNaturalSize = true;
2046 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
2052 UPDATE_LAYOUT_SIZE |
2057 void Controller::Impl::ClearStyleData()
2059 mModel->mLogicalModel->mColorRuns.Clear();
2060 mModel->mLogicalModel->ClearFontDescriptionRuns();
2061 mModel->mLogicalModel->ClearStrikethroughRuns();
2062 mModel->mLogicalModel->ClearUnderlineRuns();
2065 void Controller::Impl::ResetScrollPosition()
2069 // Reset the scroll position.
2070 mModel->mScrollPosition = Vector2::ZERO;
2071 mEventData->mScrollAfterUpdatePosition = true;
2075 } // namespace Dali::Toolkit::Text