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";
59 namespace Dali::Toolkit::Text
63 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
65 // Sets the default text's color.
66 inputStyle.textColor = textColor;
67 inputStyle.isDefaultColor = true;
69 inputStyle.familyName.clear();
70 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
71 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
72 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
73 inputStyle.size = 0.f;
75 inputStyle.lineSpacing = 0.f;
77 inputStyle.underlineProperties.clear();
78 inputStyle.shadowProperties.clear();
79 inputStyle.embossProperties.clear();
80 inputStyle.outlineProperties.clear();
82 inputStyle.isFamilyDefined = false;
83 inputStyle.isWeightDefined = false;
84 inputStyle.isWidthDefined = false;
85 inputStyle.isSlantDefined = false;
86 inputStyle.isSizeDefined = false;
88 inputStyle.isLineSpacingDefined = false;
90 inputStyle.isUnderlineDefined = false;
91 inputStyle.isShadowDefined = false;
92 inputStyle.isEmbossDefined = false;
93 inputStyle.isOutlineDefined = false;
95 // Sets the default font's family name, weight, width, slant and size.
98 if(fontDefaults->familyDefined)
100 inputStyle.familyName = fontDefaults->mFontDescription.family;
101 inputStyle.isFamilyDefined = true;
104 if(fontDefaults->weightDefined)
106 inputStyle.weight = fontDefaults->mFontDescription.weight;
107 inputStyle.isWeightDefined = true;
110 if(fontDefaults->widthDefined)
112 inputStyle.width = fontDefaults->mFontDescription.width;
113 inputStyle.isWidthDefined = true;
116 if(fontDefaults->slantDefined)
118 inputStyle.slant = fontDefaults->mFontDescription.slant;
119 inputStyle.isSlantDefined = true;
122 if(fontDefaults->sizeDefined)
124 inputStyle.size = fontDefaults->mDefaultPointSize;
125 inputStyle.isSizeDefined = true;
130 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
132 EventData* eventData = impl.mEventData;
134 if(nullptr == eventData)
136 // Nothing to do if there is no text input.
140 DecoratorPtr& decorator = eventData->mDecorator;
143 // Nothing to do if there is no decorator.
147 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
149 if(eventData->mState != newState)
151 eventData->mPreviousState = eventData->mState;
152 eventData->mState = newState;
154 switch(eventData->mState)
156 case EventData::INACTIVE:
158 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
159 decorator->StopCursorBlink();
160 decorator->SetHandleActive(GRAB_HANDLE, false);
161 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
162 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
163 decorator->SetHighlightActive(false);
164 decorator->SetPopupActive(false);
165 eventData->mDecoratorUpdated = true;
169 case EventData::INTERRUPTED:
171 decorator->SetHandleActive(GRAB_HANDLE, false);
172 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
173 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
174 decorator->SetHighlightActive(false);
175 decorator->SetPopupActive(false);
176 eventData->mDecoratorUpdated = true;
180 case EventData::SELECTING:
182 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
183 decorator->StopCursorBlink();
184 decorator->SetHandleActive(GRAB_HANDLE, false);
185 if(eventData->mGrabHandleEnabled)
187 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
188 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
190 decorator->SetHighlightActive(true);
191 if(eventData->mGrabHandlePopupEnabled)
193 impl.SetPopupButtons();
194 decorator->SetPopupActive(true);
196 eventData->mDecoratorUpdated = true;
200 case EventData::EDITING:
202 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
203 if(eventData->mCursorBlinkEnabled)
205 decorator->StartCursorBlink();
207 // Grab handle is not shown until a tap is received whilst EDITING
208 decorator->SetHandleActive(GRAB_HANDLE, false);
209 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
210 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
211 decorator->SetHighlightActive(false);
212 if(eventData->mGrabHandlePopupEnabled)
214 decorator->SetPopupActive(false);
216 eventData->mDecoratorUpdated = true;
219 case EventData::EDITING_WITH_POPUP:
221 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
223 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
224 if(eventData->mCursorBlinkEnabled)
226 decorator->StartCursorBlink();
228 if(eventData->mSelectionEnabled)
230 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
231 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
232 decorator->SetHighlightActive(false);
234 else if(eventData->mGrabHandleEnabled)
236 decorator->SetHandleActive(GRAB_HANDLE, true);
238 if(eventData->mGrabHandlePopupEnabled)
240 impl.SetPopupButtons();
241 decorator->SetPopupActive(true);
243 eventData->mDecoratorUpdated = true;
246 case EventData::EDITING_WITH_GRAB_HANDLE:
248 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
250 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
251 if(eventData->mCursorBlinkEnabled)
253 decorator->StartCursorBlink();
255 // Grab handle is not shown until a tap is received whilst EDITING
256 if(eventData->mGrabHandleEnabled)
258 decorator->SetHandleActive(GRAB_HANDLE, true);
260 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
261 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
262 decorator->SetHighlightActive(false);
263 if(eventData->mGrabHandlePopupEnabled)
265 decorator->SetPopupActive(false);
267 eventData->mDecoratorUpdated = true;
271 case EventData::SELECTION_HANDLE_PANNING:
273 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
274 decorator->StopCursorBlink();
275 decorator->SetHandleActive(GRAB_HANDLE, false);
276 if(eventData->mGrabHandleEnabled)
278 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
279 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
281 decorator->SetHighlightActive(true);
282 if(eventData->mGrabHandlePopupEnabled)
284 decorator->SetPopupActive(false);
286 eventData->mDecoratorUpdated = true;
290 case EventData::GRAB_HANDLE_PANNING:
292 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
294 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
295 if(eventData->mCursorBlinkEnabled)
297 decorator->StartCursorBlink();
299 if(eventData->mGrabHandleEnabled)
301 decorator->SetHandleActive(GRAB_HANDLE, true);
303 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
304 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
305 decorator->SetHighlightActive(false);
306 if(eventData->mGrabHandlePopupEnabled)
308 decorator->SetPopupActive(false);
310 eventData->mDecoratorUpdated = true;
314 case EventData::EDITING_WITH_PASTE_POPUP:
316 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
318 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
319 if(eventData->mCursorBlinkEnabled)
321 decorator->StartCursorBlink();
324 if(eventData->mGrabHandleEnabled)
326 decorator->SetHandleActive(GRAB_HANDLE, true);
328 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
329 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
330 decorator->SetHighlightActive(false);
332 if(eventData->mGrabHandlePopupEnabled)
334 impl.SetPopupButtons();
335 decorator->SetPopupActive(true);
337 eventData->mDecoratorUpdated = true;
341 case EventData::TEXT_PANNING:
343 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
344 decorator->StopCursorBlink();
345 decorator->SetHandleActive(GRAB_HANDLE, false);
346 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
347 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
349 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
350 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
351 decorator->SetHighlightActive(true);
354 if(eventData->mGrabHandlePopupEnabled)
356 decorator->SetPopupActive(false);
359 eventData->mDecoratorUpdated = true;
366 void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment)
368 EventData* eventData = impl.mEventData;
370 // Set the flag to redo the alignment operation
371 impl.mOperationsPending = static_cast<Controller::OperationsMask>(impl.mOperationsPending | Controller::OperationsMask::ALIGN);
375 // Note: mUpdateAlignment is currently only needed for horizontal alignment
376 eventData->mUpdateAlignment = needFullAlignment;
378 // Update the cursor if it's in editing mode
379 if(EventData::IsEditingState(eventData->mState))
381 impl.ChangeState(EventData::EDITING);
382 eventData->mUpdateCursorPosition = true;
387 } // unnamed Namespace
389 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
390 : mDecorator(decorator),
391 mInputMethodContext(inputMethodContext),
392 mPlaceholderFont(nullptr),
393 mPlaceholderTextActive(),
394 mPlaceholderTextInactive(),
395 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
397 mInputStyleChangedQueue(),
398 mPreviousState(INACTIVE),
400 mPrimaryCursorPosition(0u),
401 mLeftSelectionPosition(0u),
402 mRightSelectionPosition(0u),
403 mPreEditStartPosition(0u),
405 mCursorHookPositionX(0.f),
406 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
407 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
408 mIsShowingPlaceholderText(false),
410 mDecoratorUpdated(false),
411 mCursorBlinkEnabled(true),
412 mGrabHandleEnabled(true),
413 mGrabHandlePopupEnabled(true),
414 mSelectionEnabled(true),
415 mUpdateCursorHookPosition(false),
416 mUpdateCursorPosition(false),
417 mUpdateGrabHandlePosition(false),
418 mUpdateLeftSelectionPosition(false),
419 mUpdateRightSelectionPosition(false),
420 mIsLeftHandleSelected(false),
421 mIsRightHandleSelected(false),
422 mUpdateHighlightBox(false),
423 mScrollAfterUpdatePosition(false),
424 mScrollAfterDelete(false),
425 mAllTextSelected(false),
426 mUpdateInputStyle(false),
427 mPasswordInput(false),
428 mCheckScrollAmount(false),
429 mIsPlaceholderPixelSize(false),
430 mIsPlaceholderElideEnabled(false),
431 mPlaceholderEllipsisFlag(false),
432 mShiftSelectionFlag(true),
433 mUpdateAlignment(false),
434 mEditingEnabled(true)
438 bool Controller::Impl::ProcessInputEvents()
440 return ControllerImplEventHandler::ProcessInputEvents(*this);
443 void Controller::Impl::SetAnchorColor(const Vector4& color)
445 mAnchorColor = color;
449 const Vector4& Controller::Impl::GetAnchorColor() const
454 void Controller::Impl::SetAnchorClickedColor(const Vector4& color)
456 mAnchorClickedColor = color;
460 const Vector4& Controller::Impl::GetAnchorClickedColor() const
462 return mAnchorClickedColor;
465 void Controller::Impl::UpdateAnchorColor()
467 if(!mAnchorControlInterface ||
468 !mMarkupProcessorEnabled ||
469 !mModel->mLogicalModel->mAnchors.Count() ||
470 !IsShowingRealText())
475 bool updateNeeded = false;
477 // The anchor color & clicked color needs to be updated with the property's color.
478 for(auto& anchor : mModel->mLogicalModel->mAnchors)
480 if(!anchor.isMarkupColorSet && !anchor.isClicked)
482 if(mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
484 ColorRun& colorRun = *(mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
485 colorRun.color = mAnchorColor;
488 if(mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
490 UnderlinedCharacterRun& underlineRun = *(mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
491 underlineRun.properties.color = mAnchorColor;
495 else if(!anchor.isMarkupClickedColorSet && anchor.isClicked)
497 if(mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
499 ColorRun& colorRun = *(mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
500 colorRun.color = mAnchorClickedColor;
503 if(mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
505 UnderlinedCharacterRun& underlineRun = *(mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
506 underlineRun.properties.color = mAnchorClickedColor;
515 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
520 void Controller::Impl::NotifyInputMethodContext()
522 if(mEventData && mEventData->mInputMethodContext)
524 CharacterIndex cursorPosition = GetLogicalCursorPosition();
525 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
526 mEventData->mInputMethodContext.NotifyCursorPosition();
530 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
532 if(mEventData && mEventData->mInputMethodContext)
534 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
535 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
539 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
541 CharacterIndex cursorPosition = 0u;
545 if((EventData::SELECTING == mEventData->mState) ||
546 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
548 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
552 cursorPosition = mEventData->mPrimaryCursorPosition;
556 return cursorPosition;
559 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
561 Length numberOfWhiteSpaces = 0u;
563 // Get the buffer to the text.
564 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
566 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
567 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
569 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
575 return numberOfWhiteSpaces;
578 void Controller::Impl::GetText(std::string& text) const
580 if(!IsShowingPlaceholderText())
582 // Retrieves the text string.
587 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
591 Length Controller::Impl::GetNumberOfCharacters() const
593 if(!IsShowingPlaceholderText())
595 return mModel->GetNumberOfCharacters();
599 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetNumberOfCharacters %p empty (but showing placeholder)\n", this);
604 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
606 // Get the total number of characters.
607 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
609 // Retrieve the text.
610 if(0u != numberOfCharacters)
612 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
616 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
618 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
619 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
621 Integration::SceneHolder sceneHolder = Integration::SceneHolder::Get(actor);
622 return static_cast<Dali::LayoutDirection::Type>(sceneHolder ? sceneHolder.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
626 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
630 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
632 if(mUpdateTextDirection)
634 // Operations that can be done only once until the text changes.
635 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
643 // Set the update info to relayout the whole text.
644 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
645 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
647 // Make sure the model is up-to-date before layouting
648 UpdateModel(onlyOnceOperations);
651 Relayouter::DoRelayout(*this,
652 Size(MAX_FLOAT, MAX_FLOAT),
653 static_cast<OperationsMask>(onlyOnceOperations |
654 LAYOUT | REORDER | UPDATE_DIRECTION),
655 naturalSize.GetVectorXY());
657 // Do not do again the only once operations.
658 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
660 // Clear the update info. This info will be set the next time the text is updated.
661 mTextUpdateInfo.Clear();
663 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
664 mTextUpdateInfo.mFullRelayoutNeeded = true;
666 mUpdateTextDirection = false;
669 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
672 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
674 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
675 mTextUpdateInfo.mStartGlyphIndex = 0u;
676 mTextUpdateInfo.mStartLineIndex = 0u;
677 numberOfCharacters = 0u;
679 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
680 if(0u == numberOfParagraphs)
682 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
683 numberOfCharacters = 0u;
685 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
687 // Nothing else to do if there are no paragraphs.
691 // Find the paragraphs to be updated.
692 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
693 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
695 // Text is being added at the end of the current text.
696 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
698 // Text is being added in a new paragraph after the last character of the text.
699 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
700 numberOfCharacters = 0u;
701 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
703 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
704 mTextUpdateInfo.mStartLineIndex = (mModel->mVisualModel->mLines.Count() > 0u) ? mModel->mVisualModel->mLines.Count() - 1u : 0u;
706 // Nothing else to do;
710 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
714 Length numberOfCharactersToUpdate = 0u;
715 if(mTextUpdateInfo.mFullRelayoutNeeded)
717 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
721 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
723 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
724 numberOfCharactersToUpdate,
725 paragraphsToBeUpdated);
728 if(0u != paragraphsToBeUpdated.Count())
730 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
731 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
732 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
734 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
735 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
737 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
738 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
739 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
740 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
742 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
743 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
745 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
749 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
753 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
754 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
757 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
759 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
762 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
764 return ControllerImplModelUpdater::Update(*this, operationsRequired);
767 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
769 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
772 float Controller::Impl::GetDefaultFontLineHeight()
774 FontId defaultFontId = 0u;
775 if(nullptr == mFontDefaults)
777 TextAbstraction::FontDescription fontDescription;
778 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
782 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
785 Text::FontMetrics fontMetrics;
786 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
788 return (fontMetrics.ascender - fontMetrics.descender);
791 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
793 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
795 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
797 RelayoutAllCharacters();
803 bool Controller::Impl::SetDefaultLineSize(float lineSize)
805 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
807 mLayoutEngine.SetDefaultLineSize(lineSize);
809 RelayoutAllCharacters();
815 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
817 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
819 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
821 RelayoutAllCharacters();
827 float Controller::Impl::GetRelativeLineSize()
829 return mLayoutEngine.GetRelativeLineSize();
832 string Controller::Impl::GetSelectedText()
835 if(EventData::SELECTING == mEventData->mState)
837 RetrieveSelection(text, false);
842 string Controller::Impl::CopyText()
845 RetrieveSelection(text, false);
846 SendSelectionToClipboard(false); // Text not modified
848 mEventData->mUpdateCursorPosition = true;
850 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
855 string Controller::Impl::CutText()
858 RetrieveSelection(text, false);
865 SendSelectionToClipboard(true); // Synchronous call to modify text
866 mOperationsPending = ALL_OPERATIONS;
868 if((0u != mModel->mLogicalModel->mText.Count()) ||
869 !IsPlaceholderAvailable())
871 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
875 PlaceholderHandler::ShowPlaceholderText(*this);
878 mEventData->mUpdateCursorPosition = true;
879 mEventData->mScrollAfterDelete = true;
883 if(nullptr != mEditableControlInterface)
885 mEditableControlInterface->TextChanged(true);
890 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
892 if(nullptr == mEventData)
894 // Nothing to do if there is no text.
898 if(mEventData->mSelectionEnabled && (pStart || pEnd))
900 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
901 uint32_t oldStart = mEventData->mLeftSelectionPosition;
902 uint32_t oldEnd = mEventData->mRightSelectionPosition;
906 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
910 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
913 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
915 ChangeState(EventData::EDITING);
916 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
917 mEventData->mUpdateCursorPosition = true;
921 ChangeState(EventData::SELECTING);
922 mEventData->mUpdateHighlightBox = true;
923 mEventData->mUpdateLeftSelectionPosition = true;
924 mEventData->mUpdateRightSelectionPosition = true;
927 if(mSelectableControlInterface != nullptr)
929 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
934 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
936 if(nullptr == mEventData)
940 return mEventData->mPrimaryCursorPosition;
943 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
945 if(nullptr == mEventData)
947 // Nothing to do if there is no text.
951 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
953 // Nothing for same cursor position.
957 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
958 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
959 mEventData->mPrimaryCursorPosition = std::min(index, length);
960 // If there is no focus, only the value is updated.
963 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
964 uint32_t oldStart = mEventData->mLeftSelectionPosition;
965 uint32_t oldEnd = mEventData->mRightSelectionPosition;
966 ChangeState(EventData::EDITING);
967 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
968 mEventData->mUpdateCursorPosition = true;
970 if(mSelectableControlInterface != nullptr && wasInSelectingState)
972 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
975 ScrollTextToMatchCursor();
978 if(nullptr != mEditableControlInterface)
980 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
986 Uint32Pair Controller::Impl::GetTextSelectionRange() const
992 range.first = mEventData->mLeftSelectionPosition;
993 range.second = mEventData->mRightSelectionPosition;
999 bool Controller::Impl::IsEditable() const
1001 return mEventData && mEventData->mEditingEnabled;
1004 void Controller::Impl::SetEditable(bool editable)
1008 mEventData->mEditingEnabled = editable;
1010 if(mEventData->mDecorator)
1012 bool decoratorEditable = editable && mIsUserInteractionEnabled;
1013 mEventData->mDecorator->SetEditable(decoratorEditable);
1014 mEventData->mDecoratorUpdated = true;
1020 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
1022 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
1024 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
1026 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
1027 mFontDefaults->mFontDescription.family = newDefaultFont;
1035 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
1037 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
1039 // Nothing to select if handles are in the same place.
1040 selectedText.clear();
1044 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1046 //Get start and end position of selection
1047 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1048 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
1050 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1051 const Length numberOfCharacters = utf32Characters.Count();
1053 // Validate the start and end selection points
1054 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
1056 //Get text as a UTF8 string
1057 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
1059 if(deleteAfterRetrieval) // Only delete text if copied successfully
1061 // Keep a copy of the current input style.
1062 InputStyle currentInputStyle;
1063 currentInputStyle.Copy(mEventData->mInputStyle);
1065 // Set as input style the style of the first deleted character.
1066 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
1068 // Compare if the input style has changed.
1069 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
1071 if(hasInputStyleChanged)
1073 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
1074 // Queue the input style changed signal.
1075 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
1078 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1080 // Mark the paragraphs to be updated.
1081 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1083 mTextUpdateInfo.mCharacterIndex = 0;
1084 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1085 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1086 mTextUpdateInfo.mClearAll = true;
1090 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1091 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1094 // Delete text between handles
1095 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1096 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1097 utf32Characters.Erase(first, last);
1099 // Will show the cursor at the first character of the selection.
1100 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1104 // Will show the cursor at the last character of the selection.
1105 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1108 mEventData->mDecoratorUpdated = true;
1112 void Controller::Impl::SetSelection(int start, int end)
1114 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1115 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1117 mEventData->mLeftSelectionPosition = start;
1118 mEventData->mRightSelectionPosition = end;
1119 mEventData->mUpdateCursorPosition = true;
1121 if(mSelectableControlInterface != nullptr)
1123 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1127 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1129 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1132 void Controller::Impl::ShowClipboard()
1134 if(EnsureClipboardCreated())
1136 mClipboard.ShowClipboard();
1140 void Controller::Impl::HideClipboard()
1142 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1144 mClipboard.HideClipboard();
1148 void Controller::Impl::SetClipboardHideEnable(bool enable)
1150 mClipboardHideEnabled = enable;
1153 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1155 if(EnsureClipboardCreated())
1157 Dali::Clipboard::ClipData data(MIME_TYPE_TEXT_PLAIN, source.c_str());
1158 return mClipboard.SetData(data); // Send clipboard data to clipboard.
1164 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1166 std::string selectedText;
1167 RetrieveSelection(selectedText, deleteAfterSending);
1168 CopyStringToClipboard(selectedText);
1169 ChangeState(EventData::EDITING);
1172 void Controller::Impl::RepositionSelectionHandles()
1174 SelectionHandleController::Reposition(*this);
1176 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1178 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1181 void Controller::Impl::SetPopupButtons()
1184 * Sets the Popup buttons to be shown depending on State.
1186 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1188 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1191 bool isEditable = IsEditable();
1192 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1194 if(EventData::SELECTING == mEventData->mState)
1196 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1199 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1202 if(!IsClipboardEmpty())
1206 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1208 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1211 if(!mEventData->mAllTextSelected)
1213 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1216 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1218 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1220 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1223 if(!IsClipboardEmpty())
1227 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1229 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1232 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1234 if(!IsClipboardEmpty())
1238 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1240 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1244 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1247 void Controller::Impl::ChangeState(EventData::State newState)
1249 ChangeTextControllerState(*this, newState);
1252 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1253 CursorInfo& cursorInfo)
1255 if(!IsShowingRealText())
1257 // Do not want to use the place-holder text to set the cursor position.
1259 // Use the line's height of the font's family set to set the cursor's size.
1260 // If there is no font's family set, use the default font.
1261 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1263 cursorInfo.lineOffset = 0.f;
1264 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1265 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1268 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1270 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1273 switch(mModel->mHorizontalAlignment)
1275 case Text::HorizontalAlignment::BEGIN:
1279 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1283 cursorInfo.primaryPosition.x = 0.f;
1287 case Text::HorizontalAlignment::CENTER:
1289 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1292 case Text::HorizontalAlignment::END:
1296 cursorInfo.primaryPosition.x = 0.f;
1300 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1306 // Nothing else to do.
1310 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1311 GetCursorPositionParameters parameters;
1312 parameters.visualModel = mModel->mVisualModel;
1313 parameters.logicalModel = mModel->mLogicalModel;
1314 parameters.metrics = mMetrics;
1315 parameters.logical = logical;
1316 parameters.isMultiline = isMultiLine;
1318 float defaultFontLineHeight = GetDefaultFontLineHeight();
1320 Text::GetCursorPosition(parameters,
1321 defaultFontLineHeight,
1324 // Adds Outline offset.
1325 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1326 cursorInfo.primaryPosition.x += outlineWidth;
1327 cursorInfo.primaryPosition.y += outlineWidth;
1328 cursorInfo.secondaryPosition.x += outlineWidth;
1329 cursorInfo.secondaryPosition.y += outlineWidth;
1333 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1335 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1336 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1338 if(0.f > cursorInfo.primaryPosition.x)
1340 cursorInfo.primaryPosition.x = 0.f;
1343 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1344 if(cursorInfo.primaryPosition.x > edgeWidth)
1346 cursorInfo.primaryPosition.x = edgeWidth;
1351 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1353 if(nullptr == mEventData)
1355 // Nothing to do if there is no text input.
1359 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1361 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1362 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1364 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1365 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1367 if(numberOfCharacters > 1u)
1369 const Script script = mModel->mLogicalModel->GetScript(index);
1370 if(HasLigatureMustBreak(script))
1372 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1373 numberOfCharacters = 1u;
1378 while(0u == numberOfCharacters)
1381 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1385 if(index < mEventData->mPrimaryCursorPosition)
1387 cursorIndex = cursorIndex < numberOfCharacters ? 0u : cursorIndex - numberOfCharacters;
1391 Length textLength = mModel->mVisualModel->mCharactersToGlyph.Count();
1392 cursorIndex = cursorIndex + numberOfCharacters > textLength ? textLength : cursorIndex + numberOfCharacters;
1395 // Will update the cursor hook position.
1396 mEventData->mUpdateCursorHookPosition = true;
1401 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1403 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1404 if(nullptr == mEventData)
1406 // Nothing to do if there is no text input.
1407 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1411 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1413 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1415 // Sets the cursor position.
1416 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1419 cursorInfo.primaryCursorHeight,
1420 cursorInfo.lineHeight);
1421 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1423 if(mEventData->mUpdateGrabHandlePosition)
1425 // Sets the grab handle position.
1426 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1428 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1429 cursorInfo.lineHeight);
1432 if(cursorInfo.isSecondaryCursor)
1434 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1435 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1436 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1437 cursorInfo.secondaryCursorHeight,
1438 cursorInfo.lineHeight);
1439 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1442 // Set which cursors are active according the state.
1443 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1445 if(cursorInfo.isSecondaryCursor)
1447 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1451 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1456 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1459 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1462 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1463 const CursorInfo& cursorInfo)
1465 SelectionHandleController::Update(*this, handleType, cursorInfo);
1468 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1470 // Clamp between -space & -alignment offset.
1472 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1474 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1475 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1476 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1478 mEventData->mDecoratorUpdated = true;
1482 mModel->mScrollPosition.x = 0.f;
1486 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1488 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1490 // Nothing to do if the text is single line.
1494 // Clamp between -space & 0.
1495 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1497 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1498 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1499 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1501 mEventData->mDecoratorUpdated = true;
1505 mModel->mScrollPosition.y = 0.f;
1509 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1511 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1513 // position is in actor's coords.
1514 const float positionEndX = position.x + cursorWidth;
1515 const float positionEndY = position.y + lineHeight;
1517 // Transform the position to decorator coords.
1518 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1519 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1521 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1522 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1524 if(decoratorPositionBeginX < 0.f)
1526 mModel->mScrollPosition.x = -position.x;
1528 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1530 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1533 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1535 if(decoratorPositionBeginY < 0.f)
1537 mModel->mScrollPosition.y = -position.y;
1539 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1541 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1543 else if(mModel->mLogicalModel->mText.Count() == 0u)
1545 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1550 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1552 // Get the current cursor position in decorator coords.
1553 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1555 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1557 // Calculate the offset to match the cursor position before the character was deleted.
1558 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1560 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1561 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1563 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1564 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1567 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1568 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1570 // Makes the new cursor position visible if needed.
1571 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1574 void Controller::Impl::ScrollTextToMatchCursor()
1576 CursorInfo cursorInfo;
1577 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1578 ScrollTextToMatchCursor(cursorInfo);
1581 void Controller::Impl::RequestRelayout()
1583 if(nullptr != mControlInterface)
1585 mControlInterface->RequestTextRelayout();
1589 void Controller::Impl::RelayoutAllCharacters()
1591 // relayout all characters
1592 mTextUpdateInfo.mCharacterIndex = 0;
1593 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1594 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1595 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1597 mTextUpdateInfo.mFullRelayoutNeeded = true;
1599 // Need to recalculate natural size
1600 mRecalculateNaturalSize = true;
1603 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1605 ChangeState(EventData::EDITING);
1611 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1613 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1616 void Controller::Impl::ProcessInputStyleChangedSignals()
1620 if(mEditableControlInterface)
1622 // Emit the input style changed signal for each mask
1623 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1624 mEventData->mInputStyleChangedQueue.end(),
1625 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1628 mEventData->mInputStyleChangedQueue.Clear();
1632 void Controller::Impl::ScrollBy(Vector2 scroll)
1634 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1636 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1637 const Vector2 currentScroll = mModel->mScrollPosition;
1639 scroll.x = -scroll.x;
1640 scroll.y = -scroll.y;
1642 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1644 mModel->mScrollPosition.x += scroll.x;
1645 ClampHorizontalScroll(layoutSize);
1648 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1650 mModel->mScrollPosition.y += scroll.y;
1651 ClampVerticalScroll(layoutSize);
1654 if(mModel->mScrollPosition != currentScroll)
1656 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1662 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1664 bool isScrollable = false;
1667 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1668 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1669 if(isHorizontalScrollEnabled || isVerticalScrollEnabled)
1671 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1672 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1673 const Vector2& scrollPosition = mModel->mScrollPosition;
1675 if(isHorizontalScrollEnabled)
1677 const float displacementX = displacement.x;
1678 const float positionX = scrollPosition.x + displacementX;
1679 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1681 isScrollable = true;
1685 if(isVerticalScrollEnabled)
1687 const float displacementY = displacement.y;
1688 const float positionY = scrollPosition.y + displacementY;
1689 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1691 isScrollable = true;
1696 return isScrollable;
1699 float Controller::Impl::GetHorizontalScrollPosition()
1701 // Scroll values are negative internally so we convert them to positive numbers
1702 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1705 float Controller::Impl::GetVerticalScrollPosition()
1707 // Scroll values are negative internally so we convert them to positive numbers
1708 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1711 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1714 return Vector3(10.f, 10.f, 10.f);
1717 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1720 return Vector2(10.f, 10.f);
1723 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1725 auto actor = Toolkit::TextAnchor::New();
1726 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1727 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1728 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1729 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1730 const Vector2 anchorSize = GetAnchorSize(anchor);
1731 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1732 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1733 actor.SetProperty(Actor::Property::NAME, anchorText);
1734 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1735 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1736 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1740 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1742 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1743 we need to create and destroy potentially many actors. Some optimization can be considered here.
1744 Maybe a "dirty" flag in mLogicalModel? */
1745 anchorActors.clear();
1746 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1748 auto actor = CreateAnchorActor(anchor);
1749 anchorActors.push_back(actor);
1753 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1755 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1757 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1762 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1765 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1767 //Underlined character runs for markup-processor
1768 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1769 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1770 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1772 if(shouldClearPreUnderlineRuns)
1774 mModel->mVisualModel->mUnderlineRuns.Clear();
1777 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1779 CharacterIndex characterIndex = it->characterRun.characterIndex;
1780 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1782 if(numberOfCharacters == 0)
1787 // Create one run for all glyphs of all run's characters that has same properties
1788 // This enhance performance and reduce the needed memory to store glyphs-runs
1789 UnderlinedGlyphRun underlineGlyphRun;
1790 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1791 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1792 //Copy properties (attributes)
1793 underlineGlyphRun.properties = it->properties;
1795 for(Length index = 1u; index < numberOfCharacters; index++)
1797 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1800 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1803 // Reset flag. The updates have been applied from logical to visual.
1804 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1807 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1809 //Strikethrough character runs from markup-processor
1810 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1811 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1812 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1814 mModel->mVisualModel->mStrikethroughRuns.Clear();
1816 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1818 CharacterIndex characterIndex = it->characterRun.characterIndex;
1819 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1821 if(numberOfCharacters == 0)
1826 StrikethroughGlyphRun strikethroughGlyphRun;
1827 strikethroughGlyphRun.properties = it->properties;
1828 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1829 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1831 for(Length index = 1u; index < numberOfCharacters; index++)
1833 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1836 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1839 // Reset flag. The updates have been applied from logical to visual.
1840 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1843 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1845 //CharacterSpacing character runs from markup-processor
1846 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1847 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1848 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1850 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1852 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1854 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1855 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1857 if(numberOfCharacters == 0)
1862 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1863 characterSpacingGlyphRun.value = it->value;
1864 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1865 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1867 for(Length index = 1u; index < numberOfCharacters; index++)
1869 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1872 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1874 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1877 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1879 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1881 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1884 UPDATE_LAYOUT_SIZE |
1889 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1890 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1894 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1897 mIsAutoScrollEnabled = enable;
1902 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1903 mIsAutoScrollEnabled = false;
1907 void Controller::Impl::SetEnableCursorBlink(bool enable)
1909 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1913 mEventData->mCursorBlinkEnabled = enable;
1915 if(!enable && mEventData->mDecorator)
1917 mEventData->mDecorator->StopCursorBlink();
1922 void Controller::Impl::SetMultiLineEnabled(bool enable)
1924 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1926 if(layout != mLayoutEngine.GetLayout())
1928 // Set the layout type.
1929 mLayoutEngine.SetLayout(layout);
1931 // Set the flags to redo the layout operations
1932 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1933 UPDATE_LAYOUT_SIZE |
1937 mTextUpdateInfo.mFullRelayoutNeeded = true;
1938 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1940 // Need to recalculate natural size
1941 mRecalculateNaturalSize = true;
1947 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1949 if(alignment != mModel->mHorizontalAlignment)
1951 // Set the alignment.
1952 mModel->mHorizontalAlignment = alignment;
1953 UpdateCursorPositionForAlignment(*this, true);
1958 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1960 if(alignment != mModel->mVerticalAlignment)
1962 // Set the alignment.
1963 mModel->mVerticalAlignment = alignment;
1964 UpdateCursorPositionForAlignment(*this, false);
1969 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1971 if(lineWrapMode != mModel->mLineWrapMode)
1973 // Update Text layout for applying wrap mode
1974 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1977 UPDATE_LAYOUT_SIZE |
1980 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1981 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1983 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1986 // Set the text wrap mode.
1987 mModel->mLineWrapMode = lineWrapMode;
1989 mTextUpdateInfo.mCharacterIndex = 0u;
1990 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1991 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1998 void Controller::Impl::SetDefaultColor(const Vector4& color)
2002 if(!IsShowingPlaceholderText())
2004 mModel->mVisualModel->SetTextColor(color);
2005 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
2010 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
2012 mIsUserInteractionEnabled = enabled;
2014 if(mEventData && mEventData->mDecorator)
2016 bool editable = mEventData->mEditingEnabled && enabled;
2017 mEventData->mDecorator->SetEditable(editable);
2018 mEventData->mDecoratorUpdated = true;
2023 void Controller::Impl::ClearFontData()
2027 mFontDefaults->mFontId = 0u; // Remove old font ID
2030 // Set flags to update the model.
2031 mTextUpdateInfo.mCharacterIndex = 0u;
2032 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
2033 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
2035 mTextUpdateInfo.mClearAll = true;
2036 mTextUpdateInfo.mFullRelayoutNeeded = true;
2037 mRecalculateNaturalSize = true;
2039 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
2045 UPDATE_LAYOUT_SIZE |
2050 void Controller::Impl::ClearStyleData()
2052 mModel->mLogicalModel->mColorRuns.Clear();
2053 mModel->mLogicalModel->ClearFontDescriptionRuns();
2054 mModel->mLogicalModel->ClearStrikethroughRuns();
2055 mModel->mLogicalModel->ClearUnderlineRuns();
2058 void Controller::Impl::ResetScrollPosition()
2062 // Reset the scroll position.
2063 mModel->mScrollPosition = Vector2::ZERO;
2064 mEventData->mScrollAfterUpdatePosition = true;
2068 } // namespace Dali::Toolkit::Text