2 * Copyright (c) 2023 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() - 1u;
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);
1018 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
1020 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
1022 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
1024 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
1025 mFontDefaults->mFontDescription.family = newDefaultFont;
1033 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
1035 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
1037 // Nothing to select if handles are in the same place.
1038 selectedText.clear();
1042 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
1044 //Get start and end position of selection
1045 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1046 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
1048 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
1049 const Length numberOfCharacters = utf32Characters.Count();
1051 // Validate the start and end selection points
1052 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
1054 //Get text as a UTF8 string
1055 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
1057 if(deleteAfterRetrieval) // Only delete text if copied successfully
1059 // Keep a copy of the current input style.
1060 InputStyle currentInputStyle;
1061 currentInputStyle.Copy(mEventData->mInputStyle);
1063 // Set as input style the style of the first deleted character.
1064 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
1066 // Compare if the input style has changed.
1067 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
1069 if(hasInputStyleChanged)
1071 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
1072 // Queue the input style changed signal.
1073 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
1076 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1078 // Mark the paragraphs to be updated.
1079 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1081 mTextUpdateInfo.mCharacterIndex = 0;
1082 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1083 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1084 mTextUpdateInfo.mClearAll = true;
1088 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1089 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1092 // Delete text between handles
1093 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1094 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1095 utf32Characters.Erase(first, last);
1097 // Will show the cursor at the first character of the selection.
1098 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1102 // Will show the cursor at the last character of the selection.
1103 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1106 mEventData->mDecoratorUpdated = true;
1110 void Controller::Impl::SetSelection(int start, int end)
1112 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1113 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1115 mEventData->mLeftSelectionPosition = start;
1116 mEventData->mRightSelectionPosition = end;
1117 mEventData->mUpdateCursorPosition = true;
1119 if(mSelectableControlInterface != nullptr)
1121 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1125 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1127 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1130 void Controller::Impl::ShowClipboard()
1132 if(EnsureClipboardCreated())
1134 mClipboard.ShowClipboard();
1138 void Controller::Impl::HideClipboard()
1140 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1142 mClipboard.HideClipboard();
1146 void Controller::Impl::SetClipboardHideEnable(bool enable)
1148 mClipboardHideEnabled = enable;
1151 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1153 if(EnsureClipboardCreated())
1155 Dali::Clipboard::ClipData data(MIME_TYPE_TEXT_PLAIN, source.c_str());
1156 return mClipboard.SetData(data); // Send clipboard data to clipboard.
1162 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1164 std::string selectedText;
1165 RetrieveSelection(selectedText, deleteAfterSending);
1166 CopyStringToClipboard(selectedText);
1167 ChangeState(EventData::EDITING);
1170 void Controller::Impl::RepositionSelectionHandles()
1172 SelectionHandleController::Reposition(*this);
1174 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1176 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1179 void Controller::Impl::SetPopupButtons()
1182 * Sets the Popup buttons to be shown depending on State.
1184 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1186 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1189 bool isEditable = IsEditable();
1190 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1192 if(EventData::SELECTING == mEventData->mState)
1194 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1197 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1200 if(!IsClipboardEmpty())
1204 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1206 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1209 if(!mEventData->mAllTextSelected)
1211 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1214 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1216 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1218 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1221 if(!IsClipboardEmpty())
1225 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1227 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1230 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1232 if(!IsClipboardEmpty())
1236 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1238 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1242 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1245 void Controller::Impl::ChangeState(EventData::State newState)
1247 ChangeTextControllerState(*this, newState);
1250 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1251 CursorInfo& cursorInfo)
1253 if(!IsShowingRealText())
1255 // Do not want to use the place-holder text to set the cursor position.
1257 // Use the line's height of the font's family set to set the cursor's size.
1258 // If there is no font's family set, use the default font.
1259 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1261 cursorInfo.lineOffset = 0.f;
1262 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1263 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1266 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1268 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1271 switch(mModel->mHorizontalAlignment)
1273 case Text::HorizontalAlignment::BEGIN:
1277 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1281 cursorInfo.primaryPosition.x = 0.f;
1285 case Text::HorizontalAlignment::CENTER:
1287 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1290 case Text::HorizontalAlignment::END:
1294 cursorInfo.primaryPosition.x = 0.f;
1298 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1304 // Nothing else to do.
1308 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1309 GetCursorPositionParameters parameters;
1310 parameters.visualModel = mModel->mVisualModel;
1311 parameters.logicalModel = mModel->mLogicalModel;
1312 parameters.metrics = mMetrics;
1313 parameters.logical = logical;
1314 parameters.isMultiline = isMultiLine;
1316 float defaultFontLineHeight = GetDefaultFontLineHeight();
1318 Text::GetCursorPosition(parameters,
1319 defaultFontLineHeight,
1322 // Adds Outline offset.
1323 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1324 cursorInfo.primaryPosition.x += outlineWidth;
1325 cursorInfo.primaryPosition.y += outlineWidth;
1326 cursorInfo.secondaryPosition.x += outlineWidth;
1327 cursorInfo.secondaryPosition.y += outlineWidth;
1331 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1333 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1334 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1336 if(0.f > cursorInfo.primaryPosition.x)
1338 cursorInfo.primaryPosition.x = 0.f;
1341 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1342 if(cursorInfo.primaryPosition.x > edgeWidth)
1344 cursorInfo.primaryPosition.x = edgeWidth;
1349 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1351 if(nullptr == mEventData)
1353 // Nothing to do if there is no text input.
1357 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1359 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1360 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1362 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1363 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1365 if(numberOfCharacters > 1u)
1367 const Script script = mModel->mLogicalModel->GetScript(index);
1368 if(HasLigatureMustBreak(script))
1370 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1371 numberOfCharacters = 1u;
1376 while(0u == numberOfCharacters)
1379 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1383 if(index < mEventData->mPrimaryCursorPosition)
1385 cursorIndex = cursorIndex < numberOfCharacters ? 0u : cursorIndex - numberOfCharacters;
1389 Length textLength = mModel->mVisualModel->mCharactersToGlyph.Count();
1390 cursorIndex = cursorIndex + numberOfCharacters > textLength ? textLength : cursorIndex + numberOfCharacters;
1393 // Will update the cursor hook position.
1394 mEventData->mUpdateCursorHookPosition = true;
1399 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1401 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1402 if(nullptr == mEventData)
1404 // Nothing to do if there is no text input.
1405 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1409 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1411 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1413 // Sets the cursor position.
1414 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1417 cursorInfo.primaryCursorHeight,
1418 cursorInfo.lineHeight);
1419 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1421 if(mEventData->mUpdateGrabHandlePosition)
1423 // Sets the grab handle position.
1424 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1426 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1427 cursorInfo.lineHeight);
1430 if(cursorInfo.isSecondaryCursor)
1432 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1433 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1434 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1435 cursorInfo.secondaryCursorHeight,
1436 cursorInfo.lineHeight);
1437 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1440 // Set which cursors are active according the state.
1441 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1443 if(cursorInfo.isSecondaryCursor)
1445 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1449 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1454 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1457 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1460 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1461 const CursorInfo& cursorInfo)
1463 SelectionHandleController::Update(*this, handleType, cursorInfo);
1466 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1468 // Clamp between -space & -alignment offset.
1470 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1472 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1473 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1474 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1476 mEventData->mDecoratorUpdated = true;
1480 mModel->mScrollPosition.x = 0.f;
1484 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1486 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1488 // Nothing to do if the text is single line.
1492 // Clamp between -space & 0.
1493 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1495 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1496 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1497 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1499 mEventData->mDecoratorUpdated = true;
1503 mModel->mScrollPosition.y = 0.f;
1507 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1509 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1511 // position is in actor's coords.
1512 const float positionEndX = position.x + cursorWidth;
1513 const float positionEndY = position.y + lineHeight;
1515 // Transform the position to decorator coords.
1516 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1517 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1519 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1520 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1522 if(decoratorPositionBeginX < 0.f)
1524 mModel->mScrollPosition.x = -position.x;
1526 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1528 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1531 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1533 if(decoratorPositionBeginY < 0.f)
1535 mModel->mScrollPosition.y = -position.y;
1537 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1539 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1541 else if(mModel->mLogicalModel->mText.Count() == 0u)
1543 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1548 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1550 // Get the current cursor position in decorator coords.
1551 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1553 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1555 // Calculate the offset to match the cursor position before the character was deleted.
1556 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1558 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1559 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1561 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1562 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1565 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1566 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1568 // Makes the new cursor position visible if needed.
1569 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1572 void Controller::Impl::ScrollTextToMatchCursor()
1574 CursorInfo cursorInfo;
1575 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1576 ScrollTextToMatchCursor(cursorInfo);
1579 void Controller::Impl::RequestRelayout()
1581 if(nullptr != mControlInterface)
1583 mControlInterface->RequestTextRelayout();
1587 void Controller::Impl::RelayoutAllCharacters()
1589 // relayout all characters
1590 mTextUpdateInfo.mCharacterIndex = 0;
1591 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1592 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1593 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1595 mTextUpdateInfo.mFullRelayoutNeeded = true;
1597 // Need to recalculate natural size
1598 mRecalculateNaturalSize = true;
1601 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1603 ChangeState(EventData::EDITING);
1609 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1611 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1614 void Controller::Impl::ProcessInputStyleChangedSignals()
1618 if(mEditableControlInterface)
1620 // Emit the input style changed signal for each mask
1621 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1622 mEventData->mInputStyleChangedQueue.end(),
1623 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1626 mEventData->mInputStyleChangedQueue.Clear();
1630 void Controller::Impl::ScrollBy(Vector2 scroll)
1632 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1634 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1635 const Vector2 currentScroll = mModel->mScrollPosition;
1637 scroll.x = -scroll.x;
1638 scroll.y = -scroll.y;
1640 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1642 mModel->mScrollPosition.x += scroll.x;
1643 ClampHorizontalScroll(layoutSize);
1646 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1648 mModel->mScrollPosition.y += scroll.y;
1649 ClampVerticalScroll(layoutSize);
1652 if(mModel->mScrollPosition != currentScroll)
1654 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1660 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1662 bool isScrollable = false;
1665 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1666 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1667 if(isHorizontalScrollEnabled || isVerticalScrollEnabled)
1669 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1670 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1671 const Vector2& scrollPosition = mModel->mScrollPosition;
1673 if(isHorizontalScrollEnabled)
1675 const float displacementX = displacement.x;
1676 const float positionX = scrollPosition.x + displacementX;
1677 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1679 isScrollable = true;
1683 if(isVerticalScrollEnabled)
1685 const float displacementY = displacement.y;
1686 const float positionY = scrollPosition.y + displacementY;
1687 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1689 isScrollable = true;
1694 return isScrollable;
1697 float Controller::Impl::GetHorizontalScrollPosition()
1699 // Scroll values are negative internally so we convert them to positive numbers
1700 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1703 float Controller::Impl::GetVerticalScrollPosition()
1705 // Scroll values are negative internally so we convert them to positive numbers
1706 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1709 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1712 return Vector3(10.f, 10.f, 10.f);
1715 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1718 return Vector2(10.f, 10.f);
1721 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1723 auto actor = Toolkit::TextAnchor::New();
1724 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1725 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1726 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1727 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1728 const Vector2 anchorSize = GetAnchorSize(anchor);
1729 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1730 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1731 actor.SetProperty(Actor::Property::NAME, anchorText);
1732 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1733 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1734 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1738 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1740 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1741 we need to create and destroy potentially many actors. Some optimization can be considered here.
1742 Maybe a "dirty" flag in mLogicalModel? */
1743 anchorActors.clear();
1744 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1746 auto actor = CreateAnchorActor(anchor);
1747 anchorActors.push_back(actor);
1751 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1753 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1755 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1760 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1763 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1765 //Underlined character runs for markup-processor
1766 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1767 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1768 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1770 if(shouldClearPreUnderlineRuns)
1772 mModel->mVisualModel->mUnderlineRuns.Clear();
1775 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1777 CharacterIndex characterIndex = it->characterRun.characterIndex;
1778 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1780 if(numberOfCharacters == 0)
1785 // Create one run for all glyphs of all run's characters that has same properties
1786 // This enhance performance and reduce the needed memory to store glyphs-runs
1787 UnderlinedGlyphRun underlineGlyphRun;
1788 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1789 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1790 //Copy properties (attributes)
1791 underlineGlyphRun.properties = it->properties;
1793 for(Length index = 1u; index < numberOfCharacters; index++)
1795 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1798 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1801 // Reset flag. The updates have been applied from logical to visual.
1802 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1805 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1807 //Strikethrough character runs from markup-processor
1808 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1809 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1810 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1812 mModel->mVisualModel->mStrikethroughRuns.Clear();
1814 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1816 CharacterIndex characterIndex = it->characterRun.characterIndex;
1817 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1819 if(numberOfCharacters == 0)
1824 StrikethroughGlyphRun strikethroughGlyphRun;
1825 strikethroughGlyphRun.properties = it->properties;
1826 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1827 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1829 for(Length index = 1u; index < numberOfCharacters; index++)
1831 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1834 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1837 // Reset flag. The updates have been applied from logical to visual.
1838 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1841 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1843 //CharacterSpacing character runs from markup-processor
1844 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1845 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1846 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1848 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1850 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1852 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1853 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1855 if(numberOfCharacters == 0)
1860 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1861 characterSpacingGlyphRun.value = it->value;
1862 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1863 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1865 for(Length index = 1u; index < numberOfCharacters; index++)
1867 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1870 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1872 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1875 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1877 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1879 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1882 UPDATE_LAYOUT_SIZE |
1887 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1888 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1892 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1895 mIsAutoScrollEnabled = enable;
1900 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1901 mIsAutoScrollEnabled = false;
1905 void Controller::Impl::SetEnableCursorBlink(bool enable)
1907 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1911 mEventData->mCursorBlinkEnabled = enable;
1913 if(!enable && mEventData->mDecorator)
1915 mEventData->mDecorator->StopCursorBlink();
1920 void Controller::Impl::SetMultiLineEnabled(bool enable)
1922 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1924 if(layout != mLayoutEngine.GetLayout())
1926 // Set the layout type.
1927 mLayoutEngine.SetLayout(layout);
1929 // Set the flags to redo the layout operations
1930 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1931 UPDATE_LAYOUT_SIZE |
1935 mTextUpdateInfo.mFullRelayoutNeeded = true;
1936 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1938 // Need to recalculate natural size
1939 mRecalculateNaturalSize = true;
1945 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1947 if(alignment != mModel->mHorizontalAlignment)
1949 // Set the alignment.
1950 mModel->mHorizontalAlignment = alignment;
1951 UpdateCursorPositionForAlignment(*this, true);
1956 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1958 if(alignment != mModel->mVerticalAlignment)
1960 // Set the alignment.
1961 mModel->mVerticalAlignment = alignment;
1962 UpdateCursorPositionForAlignment(*this, false);
1967 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1969 if(lineWrapMode != mModel->mLineWrapMode)
1971 // Update Text layout for applying wrap mode
1972 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1975 UPDATE_LAYOUT_SIZE |
1978 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1979 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1981 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1984 // Set the text wrap mode.
1985 mModel->mLineWrapMode = lineWrapMode;
1987 mTextUpdateInfo.mCharacterIndex = 0u;
1988 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1989 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1996 void Controller::Impl::SetDefaultColor(const Vector4& color)
2000 if(!IsShowingPlaceholderText())
2002 mModel->mVisualModel->SetTextColor(color);
2003 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
2008 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
2010 mIsUserInteractionEnabled = enabled;
2012 if(mEventData && mEventData->mDecorator)
2014 bool editable = mEventData->mEditingEnabled && enabled;
2015 mEventData->mDecorator->SetEditable(editable);
2019 void Controller::Impl::ClearFontData()
2023 mFontDefaults->mFontId = 0u; // Remove old font ID
2026 // Set flags to update the model.
2027 mTextUpdateInfo.mCharacterIndex = 0u;
2028 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
2029 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
2031 mTextUpdateInfo.mClearAll = true;
2032 mTextUpdateInfo.mFullRelayoutNeeded = true;
2033 mRecalculateNaturalSize = true;
2035 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
2041 UPDATE_LAYOUT_SIZE |
2046 void Controller::Impl::ClearStyleData()
2048 mModel->mLogicalModel->mColorRuns.Clear();
2049 mModel->mLogicalModel->ClearFontDescriptionRuns();
2050 mModel->mLogicalModel->ClearStrikethroughRuns();
2051 mModel->mLogicalModel->ClearUnderlineRuns();
2054 void Controller::Impl::ResetScrollPosition()
2058 // Reset the scroll position.
2059 mModel->mScrollPosition = Vector2::ZERO;
2060 mEventData->mScrollAfterUpdatePosition = true;
2064 } // namespace Dali::Toolkit::Text