2 * Copyright (c) 2022 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/text-controller-impl.h>
22 #include <dali/devel-api/adaptor-framework/window-devel.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/actors/layer.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
30 #include <dali-toolkit/internal/text/text-control-interface.h>
31 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
32 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
34 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-relayouter.h>
36 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
37 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
38 #include <dali-toolkit/internal/text/text-run-container.h>
39 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
49 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
51 const std::string EMPTY_STRING("");
55 namespace Dali::Toolkit::Text
59 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
61 // Sets the default text's color.
62 inputStyle.textColor = textColor;
63 inputStyle.isDefaultColor = true;
65 inputStyle.familyName.clear();
66 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
67 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
68 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
69 inputStyle.size = 0.f;
71 inputStyle.lineSpacing = 0.f;
73 inputStyle.underlineProperties.clear();
74 inputStyle.shadowProperties.clear();
75 inputStyle.embossProperties.clear();
76 inputStyle.outlineProperties.clear();
78 inputStyle.isFamilyDefined = false;
79 inputStyle.isWeightDefined = false;
80 inputStyle.isWidthDefined = false;
81 inputStyle.isSlantDefined = false;
82 inputStyle.isSizeDefined = false;
84 inputStyle.isLineSpacingDefined = false;
86 inputStyle.isUnderlineDefined = false;
87 inputStyle.isShadowDefined = false;
88 inputStyle.isEmbossDefined = false;
89 inputStyle.isOutlineDefined = false;
91 // Sets the default font's family name, weight, width, slant and size.
94 if(fontDefaults->familyDefined)
96 inputStyle.familyName = fontDefaults->mFontDescription.family;
97 inputStyle.isFamilyDefined = true;
100 if(fontDefaults->weightDefined)
102 inputStyle.weight = fontDefaults->mFontDescription.weight;
103 inputStyle.isWeightDefined = true;
106 if(fontDefaults->widthDefined)
108 inputStyle.width = fontDefaults->mFontDescription.width;
109 inputStyle.isWidthDefined = true;
112 if(fontDefaults->slantDefined)
114 inputStyle.slant = fontDefaults->mFontDescription.slant;
115 inputStyle.isSlantDefined = true;
118 if(fontDefaults->sizeDefined)
120 inputStyle.size = fontDefaults->mDefaultPointSize;
121 inputStyle.isSizeDefined = true;
126 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
128 EventData* eventData = impl.mEventData;
130 if(nullptr == eventData)
132 // Nothing to do if there is no text input.
136 DecoratorPtr& decorator = eventData->mDecorator;
139 // Nothing to do if there is no decorator.
143 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
145 if(eventData->mState != newState)
147 eventData->mPreviousState = eventData->mState;
148 eventData->mState = newState;
150 switch(eventData->mState)
152 case EventData::INACTIVE:
154 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
155 decorator->StopCursorBlink();
156 decorator->SetHandleActive(GRAB_HANDLE, false);
157 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
158 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
159 decorator->SetHighlightActive(false);
160 decorator->SetPopupActive(false);
161 eventData->mDecoratorUpdated = true;
165 case EventData::INTERRUPTED:
167 decorator->SetHandleActive(GRAB_HANDLE, false);
168 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
169 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
170 decorator->SetHighlightActive(false);
171 decorator->SetPopupActive(false);
172 eventData->mDecoratorUpdated = true;
176 case EventData::SELECTING:
178 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
179 decorator->StopCursorBlink();
180 decorator->SetHandleActive(GRAB_HANDLE, false);
181 if(eventData->mGrabHandleEnabled)
183 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
184 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
186 decorator->SetHighlightActive(true);
187 if(eventData->mGrabHandlePopupEnabled)
189 impl.SetPopupButtons();
190 decorator->SetPopupActive(true);
192 eventData->mDecoratorUpdated = true;
196 case EventData::EDITING:
198 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
199 if(eventData->mCursorBlinkEnabled)
201 decorator->StartCursorBlink();
203 // Grab handle is not shown until a tap is received whilst EDITING
204 decorator->SetHandleActive(GRAB_HANDLE, false);
205 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
206 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
207 decorator->SetHighlightActive(false);
208 if(eventData->mGrabHandlePopupEnabled)
210 decorator->SetPopupActive(false);
212 eventData->mDecoratorUpdated = true;
215 case EventData::EDITING_WITH_POPUP:
217 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
219 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
220 if(eventData->mCursorBlinkEnabled)
222 decorator->StartCursorBlink();
224 if(eventData->mSelectionEnabled)
226 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
227 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
228 decorator->SetHighlightActive(false);
230 else if(eventData->mGrabHandleEnabled)
232 decorator->SetHandleActive(GRAB_HANDLE, true);
234 if(eventData->mGrabHandlePopupEnabled)
236 impl.SetPopupButtons();
237 decorator->SetPopupActive(true);
239 eventData->mDecoratorUpdated = true;
242 case EventData::EDITING_WITH_GRAB_HANDLE:
244 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
246 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
247 if(eventData->mCursorBlinkEnabled)
249 decorator->StartCursorBlink();
251 // Grab handle is not shown until a tap is received whilst EDITING
252 if(eventData->mGrabHandleEnabled)
254 decorator->SetHandleActive(GRAB_HANDLE, true);
256 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
257 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
258 decorator->SetHighlightActive(false);
259 if(eventData->mGrabHandlePopupEnabled)
261 decorator->SetPopupActive(false);
263 eventData->mDecoratorUpdated = true;
267 case EventData::SELECTION_HANDLE_PANNING:
269 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
270 decorator->StopCursorBlink();
271 decorator->SetHandleActive(GRAB_HANDLE, false);
272 if(eventData->mGrabHandleEnabled)
274 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
275 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
277 decorator->SetHighlightActive(true);
278 if(eventData->mGrabHandlePopupEnabled)
280 decorator->SetPopupActive(false);
282 eventData->mDecoratorUpdated = true;
286 case EventData::GRAB_HANDLE_PANNING:
288 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
290 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
291 if(eventData->mCursorBlinkEnabled)
293 decorator->StartCursorBlink();
295 if(eventData->mGrabHandleEnabled)
297 decorator->SetHandleActive(GRAB_HANDLE, true);
299 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
300 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
301 decorator->SetHighlightActive(false);
302 if(eventData->mGrabHandlePopupEnabled)
304 decorator->SetPopupActive(false);
306 eventData->mDecoratorUpdated = true;
310 case EventData::EDITING_WITH_PASTE_POPUP:
312 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
314 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
315 if(eventData->mCursorBlinkEnabled)
317 decorator->StartCursorBlink();
320 if(eventData->mGrabHandleEnabled)
322 decorator->SetHandleActive(GRAB_HANDLE, true);
324 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
325 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
326 decorator->SetHighlightActive(false);
328 if(eventData->mGrabHandlePopupEnabled)
330 impl.SetPopupButtons();
331 decorator->SetPopupActive(true);
333 eventData->mDecoratorUpdated = true;
337 case EventData::TEXT_PANNING:
339 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
340 decorator->StopCursorBlink();
341 decorator->SetHandleActive(GRAB_HANDLE, false);
342 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
343 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
345 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
346 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
347 decorator->SetHighlightActive(true);
350 if(eventData->mGrabHandlePopupEnabled)
352 decorator->SetPopupActive(false);
355 eventData->mDecoratorUpdated = true;
362 } // unnamed Namespace
364 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
365 : mDecorator(decorator),
366 mInputMethodContext(inputMethodContext),
367 mPlaceholderFont(nullptr),
368 mPlaceholderTextActive(),
369 mPlaceholderTextInactive(),
370 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
372 mInputStyleChangedQueue(),
373 mPreviousState(INACTIVE),
375 mPrimaryCursorPosition(0u),
376 mLeftSelectionPosition(0u),
377 mRightSelectionPosition(0u),
378 mPreEditStartPosition(0u),
380 mCursorHookPositionX(0.f),
381 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
382 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
383 mIsShowingPlaceholderText(false),
385 mDecoratorUpdated(false),
386 mCursorBlinkEnabled(true),
387 mGrabHandleEnabled(true),
388 mGrabHandlePopupEnabled(true),
389 mSelectionEnabled(true),
390 mUpdateCursorHookPosition(false),
391 mUpdateCursorPosition(false),
392 mUpdateGrabHandlePosition(false),
393 mUpdateLeftSelectionPosition(false),
394 mUpdateRightSelectionPosition(false),
395 mIsLeftHandleSelected(false),
396 mIsRightHandleSelected(false),
397 mUpdateHighlightBox(false),
398 mScrollAfterUpdatePosition(false),
399 mScrollAfterDelete(false),
400 mAllTextSelected(false),
401 mUpdateInputStyle(false),
402 mPasswordInput(false),
403 mCheckScrollAmount(false),
404 mIsPlaceholderPixelSize(false),
405 mIsPlaceholderElideEnabled(false),
406 mPlaceholderEllipsisFlag(false),
407 mShiftSelectionFlag(true),
408 mUpdateAlignment(false),
409 mEditingEnabled(true)
413 bool Controller::Impl::ProcessInputEvents()
415 return ControllerImplEventHandler::ProcessInputEvents(*this);
418 void Controller::Impl::NotifyInputMethodContext()
420 if(mEventData && mEventData->mInputMethodContext)
422 CharacterIndex cursorPosition = GetLogicalCursorPosition();
424 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
426 // Update the cursor position by removing the initial white spaces.
427 if(cursorPosition < numberOfWhiteSpaces)
433 cursorPosition -= numberOfWhiteSpaces;
436 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
437 mEventData->mInputMethodContext.NotifyCursorPosition();
441 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
443 if(mEventData && mEventData->mInputMethodContext)
445 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
446 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
450 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
452 CharacterIndex cursorPosition = 0u;
456 if((EventData::SELECTING == mEventData->mState) ||
457 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
459 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
463 cursorPosition = mEventData->mPrimaryCursorPosition;
467 return cursorPosition;
470 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
472 Length numberOfWhiteSpaces = 0u;
474 // Get the buffer to the text.
475 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
477 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
478 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
480 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
486 return numberOfWhiteSpaces;
489 void Controller::Impl::GetText(std::string& text) const
491 if(!IsShowingPlaceholderText())
493 // Retrieves the text string.
498 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
502 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
504 // Get the total number of characters.
505 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
507 // Retrieve the text.
508 if(0u != numberOfCharacters)
510 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
514 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
516 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
517 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
519 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
523 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
527 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
529 if(mUpdateTextDirection)
531 // Operations that can be done only once until the text changes.
532 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
540 // Set the update info to relayout the whole text.
541 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
542 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
544 // Make sure the model is up-to-date before layouting
545 UpdateModel(onlyOnceOperations);
548 Relayouter::DoRelayout(*this,
549 Size(MAX_FLOAT, MAX_FLOAT),
550 static_cast<OperationsMask>(onlyOnceOperations |
551 LAYOUT | REORDER | UPDATE_DIRECTION),
552 naturalSize.GetVectorXY());
554 // Do not do again the only once operations.
555 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
557 // Clear the update info. This info will be set the next time the text is updated.
558 mTextUpdateInfo.Clear();
560 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
561 mTextUpdateInfo.mFullRelayoutNeeded = true;
563 mUpdateTextDirection = false;
566 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
569 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
571 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
572 mTextUpdateInfo.mStartGlyphIndex = 0u;
573 mTextUpdateInfo.mStartLineIndex = 0u;
574 numberOfCharacters = 0u;
576 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
577 if(0u == numberOfParagraphs)
579 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
580 numberOfCharacters = 0u;
582 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
584 // Nothing else to do if there are no paragraphs.
588 // Find the paragraphs to be updated.
589 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
590 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
592 // Text is being added at the end of the current text.
593 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
595 // Text is being added in a new paragraph after the last character of the text.
596 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
597 numberOfCharacters = 0u;
598 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
600 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
601 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
603 // Nothing else to do;
607 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
611 Length numberOfCharactersToUpdate = 0u;
612 if(mTextUpdateInfo.mFullRelayoutNeeded)
614 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
618 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
620 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
621 numberOfCharactersToUpdate,
622 paragraphsToBeUpdated);
625 if(0u != paragraphsToBeUpdated.Count())
627 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
628 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
629 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
631 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
632 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
634 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
635 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
636 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
637 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
639 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
640 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
642 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
646 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
650 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
651 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
654 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
656 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
659 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
661 return ControllerImplModelUpdater::Update(*this, operationsRequired);
664 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
666 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
669 float Controller::Impl::GetDefaultFontLineHeight()
671 FontId defaultFontId = 0u;
672 if(nullptr == mFontDefaults)
674 TextAbstraction::FontDescription fontDescription;
675 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
679 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
682 Text::FontMetrics fontMetrics;
683 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
685 return (fontMetrics.ascender - fontMetrics.descender);
688 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
690 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
692 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
694 RelayoutForNewLineSize();
700 bool Controller::Impl::SetDefaultLineSize(float lineSize)
702 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
704 mLayoutEngine.SetDefaultLineSize(lineSize);
706 RelayoutForNewLineSize();
712 string Controller::Impl::GetSelectedText()
715 if(EventData::SELECTING == mEventData->mState)
717 RetrieveSelection(text, false);
722 string Controller::Impl::CopyText()
725 RetrieveSelection(text, false);
726 SendSelectionToClipboard(false); // Text not modified
728 mEventData->mUpdateCursorPosition = true;
730 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
735 string Controller::Impl::CutText()
738 RetrieveSelection(text, false);
745 SendSelectionToClipboard(true); // Synchronous call to modify text
746 mOperationsPending = ALL_OPERATIONS;
748 if((0u != mModel->mLogicalModel->mText.Count()) ||
749 !IsPlaceholderAvailable())
751 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
755 PlaceholderHandler::ShowPlaceholderText(*this);
758 mEventData->mUpdateCursorPosition = true;
759 mEventData->mScrollAfterDelete = true;
763 if(nullptr != mEditableControlInterface)
765 mEditableControlInterface->TextChanged(true);
770 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
772 if(nullptr == mEventData)
774 // Nothing to do if there is no text.
778 if(mEventData->mSelectionEnabled && (pStart || pEnd))
780 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
781 uint32_t oldStart = mEventData->mLeftSelectionPosition;
782 uint32_t oldEnd = mEventData->mRightSelectionPosition;
786 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
790 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
793 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
795 ChangeState(EventData::EDITING);
796 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
797 mEventData->mUpdateCursorPosition = true;
801 ChangeState(EventData::SELECTING);
802 mEventData->mUpdateHighlightBox = true;
803 mEventData->mUpdateLeftSelectionPosition = true;
804 mEventData->mUpdateRightSelectionPosition = true;
807 if(mSelectableControlInterface != nullptr)
809 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
814 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
816 if(nullptr == mEventData)
820 return mEventData->mPrimaryCursorPosition;
823 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
825 if(nullptr == mEventData)
827 // Nothing to do if there is no text.
831 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
833 // Nothing for same cursor position.
837 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
838 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
839 mEventData->mPrimaryCursorPosition = std::min(index, length);
840 // If there is no focus, only the value is updated.
843 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
844 uint32_t oldStart = mEventData->mLeftSelectionPosition;
845 uint32_t oldEnd = mEventData->mRightSelectionPosition;
846 ChangeState(EventData::EDITING);
847 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
848 mEventData->mUpdateCursorPosition = true;
850 if(mSelectableControlInterface != nullptr && wasInSelectingState)
852 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
855 ScrollTextToMatchCursor();
858 if(nullptr != mEditableControlInterface)
860 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
866 Uint32Pair Controller::Impl::GetTextSelectionRange() const
872 range.first = mEventData->mLeftSelectionPosition;
873 range.second = mEventData->mRightSelectionPosition;
879 bool Controller::Impl::IsEditable() const
881 return mEventData && mEventData->mEditingEnabled;
884 void Controller::Impl::SetEditable(bool editable)
888 mEventData->mEditingEnabled = editable;
890 if(mEventData->mDecorator)
892 mEventData->mDecorator->SetEditable(editable);
897 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
899 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
901 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
903 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
904 mFontDefaults->mFontDescription.family = newDefaultFont;
912 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
914 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
916 // Nothing to select if handles are in the same place.
917 selectedText.clear();
921 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
923 //Get start and end position of selection
924 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
925 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
927 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
928 const Length numberOfCharacters = utf32Characters.Count();
930 // Validate the start and end selection points
931 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
933 //Get text as a UTF8 string
934 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
936 if(deleteAfterRetrieval) // Only delete text if copied successfully
938 // Keep a copy of the current input style.
939 InputStyle currentInputStyle;
940 currentInputStyle.Copy(mEventData->mInputStyle);
942 // Set as input style the style of the first deleted character.
943 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
945 // Compare if the input style has changed.
946 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
948 if(hasInputStyleChanged)
950 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
951 // Queue the input style changed signal.
952 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
955 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
957 // Mark the paragraphs to be updated.
958 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
960 mTextUpdateInfo.mCharacterIndex = 0;
961 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
962 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
963 mTextUpdateInfo.mClearAll = true;
967 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
968 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
971 // Delete text between handles
972 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
973 Vector<Character>::Iterator last = first + lengthOfSelectedText;
974 utf32Characters.Erase(first, last);
976 // Will show the cursor at the first character of the selection.
977 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
981 // Will show the cursor at the last character of the selection.
982 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
985 mEventData->mDecoratorUpdated = true;
989 void Controller::Impl::SetSelection(int start, int end)
991 uint32_t oldStart = mEventData->mLeftSelectionPosition;
992 uint32_t oldEnd = mEventData->mRightSelectionPosition;
994 mEventData->mLeftSelectionPosition = start;
995 mEventData->mRightSelectionPosition = end;
996 mEventData->mUpdateCursorPosition = true;
998 if(mSelectableControlInterface != nullptr)
1000 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1004 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1006 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1009 void Controller::Impl::ShowClipboard()
1013 mClipboard.ShowClipboard();
1017 void Controller::Impl::HideClipboard()
1019 if(mClipboard && mClipboardHideEnabled)
1021 mClipboard.HideClipboard();
1025 void Controller::Impl::SetClipboardHideEnable(bool enable)
1027 mClipboardHideEnabled = enable;
1030 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1032 //Send string to clipboard
1033 return (mClipboard && mClipboard.SetItem(source));
1036 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1038 std::string selectedText;
1039 RetrieveSelection(selectedText, deleteAfterSending);
1040 CopyStringToClipboard(selectedText);
1041 ChangeState(EventData::EDITING);
1044 void Controller::Impl::RequestGetTextFromClipboard()
1048 mClipboard.RequestItem();
1052 void Controller::Impl::RepositionSelectionHandles()
1054 SelectionHandleController::Reposition(*this);
1056 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1058 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1061 void Controller::Impl::SetPopupButtons()
1064 * Sets the Popup buttons to be shown depending on State.
1066 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1068 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1071 bool isEditable = IsEditable();
1072 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1074 if(EventData::SELECTING == mEventData->mState)
1076 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1079 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1082 if(!IsClipboardEmpty())
1086 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1088 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1091 if(!mEventData->mAllTextSelected)
1093 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1096 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1098 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1100 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1103 if(!IsClipboardEmpty())
1107 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1109 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1112 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1114 if(!IsClipboardEmpty())
1118 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1120 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1124 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1127 void Controller::Impl::ChangeState(EventData::State newState)
1129 ChangeTextControllerState(*this, newState);
1132 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1133 CursorInfo& cursorInfo)
1135 if(!IsShowingRealText())
1137 // Do not want to use the place-holder text to set the cursor position.
1139 // Use the line's height of the font's family set to set the cursor's size.
1140 // If there is no font's family set, use the default font.
1141 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1143 cursorInfo.lineOffset = 0.f;
1144 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1145 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1148 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1150 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1153 switch(mModel->mHorizontalAlignment)
1155 case Text::HorizontalAlignment::BEGIN:
1159 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1163 cursorInfo.primaryPosition.x = 0.f;
1167 case Text::HorizontalAlignment::CENTER:
1169 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1172 case Text::HorizontalAlignment::END:
1176 cursorInfo.primaryPosition.x = 0.f;
1180 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1186 // Nothing else to do.
1190 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1191 GetCursorPositionParameters parameters;
1192 parameters.visualModel = mModel->mVisualModel;
1193 parameters.logicalModel = mModel->mLogicalModel;
1194 parameters.metrics = mMetrics;
1195 parameters.logical = logical;
1196 parameters.isMultiline = isMultiLine;
1198 float defaultFontLineHeight = GetDefaultFontLineHeight();
1200 Text::GetCursorPosition(parameters,
1201 defaultFontLineHeight,
1204 // Adds Outline offset.
1205 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1206 cursorInfo.primaryPosition.x += outlineWidth;
1207 cursorInfo.primaryPosition.y += outlineWidth;
1208 cursorInfo.secondaryPosition.x += outlineWidth;
1209 cursorInfo.secondaryPosition.y += outlineWidth;
1213 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1215 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1216 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1218 if(0.f > cursorInfo.primaryPosition.x)
1220 cursorInfo.primaryPosition.x = 0.f;
1223 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1224 if(cursorInfo.primaryPosition.x > edgeWidth)
1226 cursorInfo.primaryPosition.x = edgeWidth;
1231 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1233 if(nullptr == mEventData)
1235 // Nothing to do if there is no text input.
1239 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1241 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1242 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1244 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1245 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1247 if(numberOfCharacters > 1u)
1249 const Script script = mModel->mLogicalModel->GetScript(index);
1250 if(HasLigatureMustBreak(script))
1252 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1253 numberOfCharacters = 1u;
1258 while(0u == numberOfCharacters)
1261 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1265 if(index < mEventData->mPrimaryCursorPosition)
1267 cursorIndex -= numberOfCharacters;
1271 cursorIndex += numberOfCharacters;
1274 // Will update the cursor hook position.
1275 mEventData->mUpdateCursorHookPosition = true;
1280 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1282 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1283 if(nullptr == mEventData)
1285 // Nothing to do if there is no text input.
1286 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1290 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1292 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1294 // Sets the cursor position.
1295 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1298 cursorInfo.primaryCursorHeight,
1299 cursorInfo.lineHeight);
1300 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1302 if(mEventData->mUpdateGrabHandlePosition)
1304 // Sets the grab handle position.
1305 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1307 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1308 cursorInfo.lineHeight);
1311 if(cursorInfo.isSecondaryCursor)
1313 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1314 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1315 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1316 cursorInfo.secondaryCursorHeight,
1317 cursorInfo.lineHeight);
1318 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1321 // Set which cursors are active according the state.
1322 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1324 if(cursorInfo.isSecondaryCursor)
1326 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1330 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1335 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1338 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1341 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1342 const CursorInfo& cursorInfo)
1344 SelectionHandleController::Update(*this, handleType, cursorInfo);
1347 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1349 // Clamp between -space & -alignment offset.
1351 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1353 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1354 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1355 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1357 mEventData->mDecoratorUpdated = true;
1361 mModel->mScrollPosition.x = 0.f;
1365 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1367 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1369 // Nothing to do if the text is single line.
1373 // Clamp between -space & 0.
1374 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1376 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1377 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1378 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1380 mEventData->mDecoratorUpdated = true;
1384 mModel->mScrollPosition.y = 0.f;
1388 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1390 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1392 // position is in actor's coords.
1393 const float positionEndX = position.x + cursorWidth;
1394 const float positionEndY = position.y + lineHeight;
1396 // Transform the position to decorator coords.
1397 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1398 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1400 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1401 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1403 if(decoratorPositionBeginX < 0.f)
1405 mModel->mScrollPosition.x = -position.x;
1407 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1409 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1412 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1414 if(decoratorPositionBeginY < 0.f)
1416 mModel->mScrollPosition.y = -position.y;
1418 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1420 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1425 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1427 // Get the current cursor position in decorator coords.
1428 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1430 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1432 // Calculate the offset to match the cursor position before the character was deleted.
1433 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1435 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1436 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1438 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1439 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1442 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1443 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1445 // Makes the new cursor position visible if needed.
1446 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1449 void Controller::Impl::ScrollTextToMatchCursor()
1451 CursorInfo cursorInfo;
1452 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1453 ScrollTextToMatchCursor(cursorInfo);
1456 void Controller::Impl::RequestRelayout()
1458 if(nullptr != mControlInterface)
1460 mControlInterface->RequestTextRelayout();
1464 void Controller::Impl::RelayoutForNewLineSize()
1466 // relayout all characters
1467 mTextUpdateInfo.mCharacterIndex = 0;
1468 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1469 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1470 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1472 mTextUpdateInfo.mFullRelayoutNeeded = true;
1474 // Need to recalculate natural size
1475 mRecalculateNaturalSize = true;
1478 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1480 ChangeState(EventData::EDITING);
1486 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1488 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1491 void Controller::Impl::ProcessInputStyleChangedSignals()
1495 if(mEditableControlInterface)
1497 // Emit the input style changed signal for each mask
1498 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1499 mEventData->mInputStyleChangedQueue.end(),
1500 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1503 mEventData->mInputStyleChangedQueue.Clear();
1507 void Controller::Impl::ScrollBy(Vector2 scroll)
1509 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1511 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1512 const Vector2 currentScroll = mModel->mScrollPosition;
1514 scroll.x = -scroll.x;
1515 scroll.y = -scroll.y;
1517 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1519 mModel->mScrollPosition.x += scroll.x;
1520 ClampHorizontalScroll(layoutSize);
1523 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1525 mModel->mScrollPosition.y += scroll.y;
1526 ClampVerticalScroll(layoutSize);
1529 if(mModel->mScrollPosition != currentScroll)
1531 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1537 float Controller::Impl::GetHorizontalScrollPosition()
1539 // Scroll values are negative internally so we convert them to positive numbers
1540 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1543 float Controller::Impl::GetVerticalScrollPosition()
1545 // Scroll values are negative internally so we convert them to positive numbers
1546 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1549 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1552 return Vector3(10.f, 10.f, 10.f);
1555 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1558 return Vector2(10.f, 10.f);
1561 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1563 auto actor = Toolkit::TextAnchor::New();
1564 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1565 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1566 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1567 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1568 const Vector2 anchorSize = GetAnchorSize(anchor);
1569 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1570 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1571 actor.SetProperty(Actor::Property::NAME, anchorText);
1572 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1573 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1574 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1578 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1580 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1581 we need to create and destroy potentially many actors. Some optimization can be considered here.
1582 Maybe a "dirty" flag in mLogicalModel? */
1583 anchorActors.clear();
1584 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1586 auto actor = CreateAnchorActor(anchor);
1587 anchorActors.push_back(actor);
1591 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1593 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1595 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1600 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1603 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1605 //Underlined character runs for markup-processor
1606 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1607 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1608 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1610 if(shouldClearPreUnderlineRuns)
1612 mModel->mVisualModel->mUnderlineRuns.Clear();
1615 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1617 CharacterIndex characterIndex = it->characterRun.characterIndex;
1618 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1619 for(Length index = 0u; index < numberOfCharacters; index++)
1621 GlyphRun underlineGlyphRun;
1622 underlineGlyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1623 underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1624 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1629 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1631 //Strikethrough character runs from markup-processor
1632 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1633 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1634 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1636 mModel->mVisualModel->mStrikethroughRuns.Clear();
1638 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1640 CharacterIndex characterIndex = it->characterRun.characterIndex;
1641 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1642 StrikethroughGlyphRun strikethroughGlyphRun;
1643 strikethroughGlyphRun.color = it->color;
1644 strikethroughGlyphRun.isColorSet = it->isColorSet;
1645 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1646 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1648 for(Length index = 1u; index < numberOfCharacters; index++)
1650 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1653 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1657 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1659 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1661 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1664 UPDATE_LAYOUT_SIZE |
1669 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1670 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1674 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1677 mIsAutoScrollEnabled = enable;
1682 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1683 mIsAutoScrollEnabled = false;
1687 void Controller::Impl::SetEnableCursorBlink(bool enable)
1689 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1693 mEventData->mCursorBlinkEnabled = enable;
1695 if(!enable && mEventData->mDecorator)
1697 mEventData->mDecorator->StopCursorBlink();
1702 void Controller::Impl::SetMultiLineEnabled(bool enable)
1704 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1706 if(layout != mLayoutEngine.GetLayout())
1708 // Set the layout type.
1709 mLayoutEngine.SetLayout(layout);
1711 // Set the flags to redo the layout operations
1712 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1713 UPDATE_LAYOUT_SIZE |
1717 mTextUpdateInfo.mFullRelayoutNeeded = true;
1718 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1720 // Need to recalculate natural size
1721 mRecalculateNaturalSize = true;
1727 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1729 if(alignment != mModel->mHorizontalAlignment)
1731 // Set the alignment.
1732 mModel->mHorizontalAlignment = alignment;
1734 // Set the flag to redo the alignment operation.
1735 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1739 mEventData->mUpdateAlignment = true;
1741 // Update the cursor if it's in editing mode
1742 if(EventData::IsEditingState(mEventData->mState))
1744 ChangeState(EventData::EDITING);
1745 mEventData->mUpdateCursorPosition = true;
1753 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1755 if(alignment != mModel->mVerticalAlignment)
1757 // Set the alignment.
1758 mModel->mVerticalAlignment = alignment;
1759 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1764 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1766 if(lineWrapMode != mModel->mLineWrapMode)
1768 // Update Text layout for applying wrap mode
1769 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1772 UPDATE_LAYOUT_SIZE |
1775 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1776 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1778 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1781 // Set the text wrap mode.
1782 mModel->mLineWrapMode = lineWrapMode;
1784 mTextUpdateInfo.mCharacterIndex = 0u;
1785 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1786 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1793 void Controller::Impl::SetDefaultColor(const Vector4& color)
1797 if(!IsShowingPlaceholderText())
1799 mModel->mVisualModel->SetTextColor(color);
1800 mModel->mLogicalModel->mColorRuns.Clear();
1801 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1806 void Controller::Impl::ClearFontData()
1810 mFontDefaults->mFontId = 0u; // Remove old font ID
1813 // Set flags to update the model.
1814 mTextUpdateInfo.mCharacterIndex = 0u;
1815 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1816 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1818 mTextUpdateInfo.mClearAll = true;
1819 mTextUpdateInfo.mFullRelayoutNeeded = true;
1820 mRecalculateNaturalSize = true;
1822 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1828 UPDATE_LAYOUT_SIZE |
1833 void Controller::Impl::ClearStyleData()
1835 mModel->mLogicalModel->mColorRuns.Clear();
1836 mModel->mLogicalModel->ClearFontDescriptionRuns();
1839 void Controller::Impl::ResetScrollPosition()
1843 // Reset the scroll position.
1844 mModel->mScrollPosition = Vector2::ZERO;
1845 mEventData->mScrollAfterUpdatePosition = true;
1849 } // namespace Dali::Toolkit::Text