2 * Copyright (c) 2021 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 * mFontSizeScale);
679 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
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 Text::GetCursorPosition(parameters,
1201 // Adds Outline offset.
1202 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1203 cursorInfo.primaryPosition.x += outlineWidth;
1204 cursorInfo.primaryPosition.y += outlineWidth;
1205 cursorInfo.secondaryPosition.x += outlineWidth;
1206 cursorInfo.secondaryPosition.y += outlineWidth;
1210 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1212 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1213 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1215 if(0.f > cursorInfo.primaryPosition.x)
1217 cursorInfo.primaryPosition.x = 0.f;
1220 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1221 if(cursorInfo.primaryPosition.x > edgeWidth)
1223 cursorInfo.primaryPosition.x = edgeWidth;
1228 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1230 if(nullptr == mEventData)
1232 // Nothing to do if there is no text input.
1236 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1238 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1239 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1241 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1242 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1244 if(numberOfCharacters > 1u)
1246 const Script script = mModel->mLogicalModel->GetScript(index);
1247 if(HasLigatureMustBreak(script))
1249 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1250 numberOfCharacters = 1u;
1255 while(0u == numberOfCharacters)
1258 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1262 if(index < mEventData->mPrimaryCursorPosition)
1264 cursorIndex -= numberOfCharacters;
1268 cursorIndex += numberOfCharacters;
1271 // Will update the cursor hook position.
1272 mEventData->mUpdateCursorHookPosition = true;
1277 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1279 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1280 if(nullptr == mEventData)
1282 // Nothing to do if there is no text input.
1283 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1287 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1289 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1291 // Sets the cursor position.
1292 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1295 cursorInfo.primaryCursorHeight,
1296 cursorInfo.lineHeight);
1297 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1299 if(mEventData->mUpdateGrabHandlePosition)
1301 // Sets the grab handle position.
1302 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1304 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1305 cursorInfo.lineHeight);
1308 if(cursorInfo.isSecondaryCursor)
1310 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1311 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1312 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1313 cursorInfo.secondaryCursorHeight,
1314 cursorInfo.lineHeight);
1315 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1318 // Set which cursors are active according the state.
1319 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1321 if(cursorInfo.isSecondaryCursor)
1323 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1327 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1332 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1335 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1338 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1339 const CursorInfo& cursorInfo)
1341 SelectionHandleController::Update(*this, handleType, cursorInfo);
1344 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1346 // Clamp between -space & -alignment offset.
1348 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1350 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1351 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1352 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1354 mEventData->mDecoratorUpdated = true;
1358 mModel->mScrollPosition.x = 0.f;
1362 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1364 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1366 // Nothing to do if the text is single line.
1370 // Clamp between -space & 0.
1371 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1373 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1374 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1375 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1377 mEventData->mDecoratorUpdated = true;
1381 mModel->mScrollPosition.y = 0.f;
1385 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1387 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1389 // position is in actor's coords.
1390 const float positionEndX = position.x + cursorWidth;
1391 const float positionEndY = position.y + lineHeight;
1393 // Transform the position to decorator coords.
1394 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1395 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1397 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1398 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1400 if(decoratorPositionBeginX < 0.f)
1402 mModel->mScrollPosition.x = -position.x;
1404 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1406 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1409 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1411 if(decoratorPositionBeginY < 0.f)
1413 mModel->mScrollPosition.y = -position.y;
1415 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1417 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1422 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1424 // Get the current cursor position in decorator coords.
1425 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1427 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1429 // Calculate the offset to match the cursor position before the character was deleted.
1430 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1432 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1433 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1435 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1436 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1439 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1440 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1442 // Makes the new cursor position visible if needed.
1443 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1446 void Controller::Impl::ScrollTextToMatchCursor()
1448 CursorInfo cursorInfo;
1449 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1450 ScrollTextToMatchCursor(cursorInfo);
1453 void Controller::Impl::RequestRelayout()
1455 if(nullptr != mControlInterface)
1457 mControlInterface->RequestTextRelayout();
1461 void Controller::Impl::RelayoutForNewLineSize()
1463 // relayout all characters
1464 mTextUpdateInfo.mCharacterIndex = 0;
1465 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1466 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1467 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1469 mTextUpdateInfo.mFullRelayoutNeeded = true;
1471 // Need to recalculate natural size
1472 mRecalculateNaturalSize = true;
1475 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1477 ChangeState(EventData::EDITING);
1483 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1485 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1488 void Controller::Impl::ProcessInputStyleChangedSignals()
1492 if(mEditableControlInterface)
1494 // Emit the input style changed signal for each mask
1495 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1496 mEventData->mInputStyleChangedQueue.end(),
1497 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1500 mEventData->mInputStyleChangedQueue.Clear();
1504 void Controller::Impl::ScrollBy(Vector2 scroll)
1506 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1508 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1509 const Vector2 currentScroll = mModel->mScrollPosition;
1511 scroll.x = -scroll.x;
1512 scroll.y = -scroll.y;
1514 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1516 mModel->mScrollPosition.x += scroll.x;
1517 ClampHorizontalScroll(layoutSize);
1520 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1522 mModel->mScrollPosition.y += scroll.y;
1523 ClampVerticalScroll(layoutSize);
1526 if(mModel->mScrollPosition != currentScroll)
1528 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1534 float Controller::Impl::GetHorizontalScrollPosition()
1536 // Scroll values are negative internally so we convert them to positive numbers
1537 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1540 float Controller::Impl::GetVerticalScrollPosition()
1542 // Scroll values are negative internally so we convert them to positive numbers
1543 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1546 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1549 return Vector3(10.f, 10.f, 10.f);
1552 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1555 return Vector2(10.f, 10.f);
1558 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1560 auto actor = Toolkit::TextAnchor::New();
1561 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1562 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1563 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1564 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1565 const Vector2 anchorSize = GetAnchorSize(anchor);
1566 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1567 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1568 actor.SetProperty(Actor::Property::NAME, anchorText);
1569 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1570 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1571 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1575 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1577 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1578 we need to create and destroy potentially many actors. Some optimization can be considered here.
1579 Maybe a "dirty" flag in mLogicalModel? */
1580 anchorActors.clear();
1581 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1583 auto actor = CreateAnchorActor(anchor);
1584 anchorActors.push_back(actor);
1588 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1590 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1592 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1597 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1600 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1602 //Underlined character runs for markup-processor
1603 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1604 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1605 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1607 if(shouldClearPreUnderlineRuns)
1609 mModel->mVisualModel->mUnderlineRuns.Clear();
1612 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1614 CharacterIndex characterIndex = it->characterRun.characterIndex;
1615 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1616 for(Length index = 0u; index < numberOfCharacters; index++)
1618 GlyphRun underlineGlyphRun;
1619 underlineGlyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1620 underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1621 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1626 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1628 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1630 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1633 UPDATE_LAYOUT_SIZE |
1638 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1639 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1643 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1646 mIsAutoScrollEnabled = enable;
1651 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1652 mIsAutoScrollEnabled = false;
1656 void Controller::Impl::SetEnableCursorBlink(bool enable)
1658 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1662 mEventData->mCursorBlinkEnabled = enable;
1664 if(!enable && mEventData->mDecorator)
1666 mEventData->mDecorator->StopCursorBlink();
1671 void Controller::Impl::SetMultiLineEnabled(bool enable)
1673 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1675 if(layout != mLayoutEngine.GetLayout())
1677 // Set the layout type.
1678 mLayoutEngine.SetLayout(layout);
1680 // Set the flags to redo the layout operations
1681 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1682 UPDATE_LAYOUT_SIZE |
1686 mTextUpdateInfo.mFullRelayoutNeeded = true;
1687 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1689 // Need to recalculate natural size
1690 mRecalculateNaturalSize = true;
1696 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1698 if(alignment != mModel->mHorizontalAlignment)
1700 // Set the alignment.
1701 mModel->mHorizontalAlignment = alignment;
1703 // Set the flag to redo the alignment operation.
1704 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1708 mEventData->mUpdateAlignment = true;
1710 // Update the cursor if it's in editing mode
1711 if(EventData::IsEditingState(mEventData->mState))
1713 ChangeState(EventData::EDITING);
1714 mEventData->mUpdateCursorPosition = true;
1722 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1724 if(alignment != mModel->mVerticalAlignment)
1726 // Set the alignment.
1727 mModel->mVerticalAlignment = alignment;
1728 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1733 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1735 if(lineWrapMode != mModel->mLineWrapMode)
1737 // Update Text layout for applying wrap mode
1738 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1741 UPDATE_LAYOUT_SIZE |
1744 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1745 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1747 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1750 // Set the text wrap mode.
1751 mModel->mLineWrapMode = lineWrapMode;
1753 mTextUpdateInfo.mCharacterIndex = 0u;
1754 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1755 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1762 void Controller::Impl::SetDefaultColor(const Vector4& color)
1766 if(!IsShowingPlaceholderText())
1768 mModel->mVisualModel->SetTextColor(color);
1769 mModel->mLogicalModel->mColorRuns.Clear();
1770 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1775 void Controller::Impl::ClearFontData()
1779 mFontDefaults->mFontId = 0u; // Remove old font ID
1782 // Set flags to update the model.
1783 mTextUpdateInfo.mCharacterIndex = 0u;
1784 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1785 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1787 mTextUpdateInfo.mClearAll = true;
1788 mTextUpdateInfo.mFullRelayoutNeeded = true;
1789 mRecalculateNaturalSize = true;
1791 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1797 UPDATE_LAYOUT_SIZE |
1802 void Controller::Impl::ClearStyleData()
1804 mModel->mLogicalModel->mColorRuns.Clear();
1805 mModel->mLogicalModel->ClearFontDescriptionRuns();
1808 void Controller::Impl::ResetScrollPosition()
1812 // Reset the scroll position.
1813 mModel->mScrollPosition = Vector2::ZERO;
1814 mEventData->mScrollAfterUpdatePosition = true;
1818 } // namespace Dali::Toolkit::Text