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>
25 #include <dali/public-api/rendering/renderer.h>
29 #include <dali-toolkit/internal/text/character-set-conversion.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
32 #include <dali-toolkit/internal/text/text-control-interface.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
34 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
36 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
37 #include <dali-toolkit/internal/text/text-controller-relayouter.h>
38 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
39 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
40 #include <dali-toolkit/internal/text/text-run-container.h>
41 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
42 #include <dali-toolkit/internal/text/underlined-glyph-run.h>
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
52 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
54 const std::string EMPTY_STRING("");
58 namespace Dali::Toolkit::Text
62 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
64 // Sets the default text's color.
65 inputStyle.textColor = textColor;
66 inputStyle.isDefaultColor = true;
68 inputStyle.familyName.clear();
69 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
70 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
71 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
72 inputStyle.size = 0.f;
74 inputStyle.lineSpacing = 0.f;
76 inputStyle.underlineProperties.clear();
77 inputStyle.shadowProperties.clear();
78 inputStyle.embossProperties.clear();
79 inputStyle.outlineProperties.clear();
81 inputStyle.isFamilyDefined = false;
82 inputStyle.isWeightDefined = false;
83 inputStyle.isWidthDefined = false;
84 inputStyle.isSlantDefined = false;
85 inputStyle.isSizeDefined = false;
87 inputStyle.isLineSpacingDefined = false;
89 inputStyle.isUnderlineDefined = false;
90 inputStyle.isShadowDefined = false;
91 inputStyle.isEmbossDefined = false;
92 inputStyle.isOutlineDefined = false;
94 // Sets the default font's family name, weight, width, slant and size.
97 if(fontDefaults->familyDefined)
99 inputStyle.familyName = fontDefaults->mFontDescription.family;
100 inputStyle.isFamilyDefined = true;
103 if(fontDefaults->weightDefined)
105 inputStyle.weight = fontDefaults->mFontDescription.weight;
106 inputStyle.isWeightDefined = true;
109 if(fontDefaults->widthDefined)
111 inputStyle.width = fontDefaults->mFontDescription.width;
112 inputStyle.isWidthDefined = true;
115 if(fontDefaults->slantDefined)
117 inputStyle.slant = fontDefaults->mFontDescription.slant;
118 inputStyle.isSlantDefined = true;
121 if(fontDefaults->sizeDefined)
123 inputStyle.size = fontDefaults->mDefaultPointSize;
124 inputStyle.isSizeDefined = true;
129 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
131 EventData* eventData = impl.mEventData;
133 if(nullptr == eventData)
135 // Nothing to do if there is no text input.
139 DecoratorPtr& decorator = eventData->mDecorator;
142 // Nothing to do if there is no decorator.
146 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
148 if(eventData->mState != newState)
150 eventData->mPreviousState = eventData->mState;
151 eventData->mState = newState;
153 switch(eventData->mState)
155 case EventData::INACTIVE:
157 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
158 decorator->StopCursorBlink();
159 decorator->SetHandleActive(GRAB_HANDLE, false);
160 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
161 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
162 decorator->SetHighlightActive(false);
163 decorator->SetPopupActive(false);
164 eventData->mDecoratorUpdated = true;
168 case EventData::INTERRUPTED:
170 decorator->SetHandleActive(GRAB_HANDLE, false);
171 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
172 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
173 decorator->SetHighlightActive(false);
174 decorator->SetPopupActive(false);
175 eventData->mDecoratorUpdated = true;
179 case EventData::SELECTING:
181 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
182 decorator->StopCursorBlink();
183 decorator->SetHandleActive(GRAB_HANDLE, false);
184 if(eventData->mGrabHandleEnabled)
186 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
187 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
189 decorator->SetHighlightActive(true);
190 if(eventData->mGrabHandlePopupEnabled)
192 impl.SetPopupButtons();
193 decorator->SetPopupActive(true);
195 eventData->mDecoratorUpdated = true;
199 case EventData::EDITING:
201 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
202 if(eventData->mCursorBlinkEnabled)
204 decorator->StartCursorBlink();
206 // Grab handle is not shown until a tap is received whilst EDITING
207 decorator->SetHandleActive(GRAB_HANDLE, false);
208 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
209 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
210 decorator->SetHighlightActive(false);
211 if(eventData->mGrabHandlePopupEnabled)
213 decorator->SetPopupActive(false);
215 eventData->mDecoratorUpdated = true;
218 case EventData::EDITING_WITH_POPUP:
220 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
222 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
223 if(eventData->mCursorBlinkEnabled)
225 decorator->StartCursorBlink();
227 if(eventData->mSelectionEnabled)
229 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
230 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
231 decorator->SetHighlightActive(false);
233 else if(eventData->mGrabHandleEnabled)
235 decorator->SetHandleActive(GRAB_HANDLE, true);
237 if(eventData->mGrabHandlePopupEnabled)
239 impl.SetPopupButtons();
240 decorator->SetPopupActive(true);
242 eventData->mDecoratorUpdated = true;
245 case EventData::EDITING_WITH_GRAB_HANDLE:
247 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
249 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
250 if(eventData->mCursorBlinkEnabled)
252 decorator->StartCursorBlink();
254 // Grab handle is not shown until a tap is received whilst EDITING
255 if(eventData->mGrabHandleEnabled)
257 decorator->SetHandleActive(GRAB_HANDLE, true);
259 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
260 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
261 decorator->SetHighlightActive(false);
262 if(eventData->mGrabHandlePopupEnabled)
264 decorator->SetPopupActive(false);
266 eventData->mDecoratorUpdated = true;
270 case EventData::SELECTION_HANDLE_PANNING:
272 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
273 decorator->StopCursorBlink();
274 decorator->SetHandleActive(GRAB_HANDLE, false);
275 if(eventData->mGrabHandleEnabled)
277 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
278 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
280 decorator->SetHighlightActive(true);
281 if(eventData->mGrabHandlePopupEnabled)
283 decorator->SetPopupActive(false);
285 eventData->mDecoratorUpdated = true;
289 case EventData::GRAB_HANDLE_PANNING:
291 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
293 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
294 if(eventData->mCursorBlinkEnabled)
296 decorator->StartCursorBlink();
298 if(eventData->mGrabHandleEnabled)
300 decorator->SetHandleActive(GRAB_HANDLE, true);
302 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
303 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
304 decorator->SetHighlightActive(false);
305 if(eventData->mGrabHandlePopupEnabled)
307 decorator->SetPopupActive(false);
309 eventData->mDecoratorUpdated = true;
313 case EventData::EDITING_WITH_PASTE_POPUP:
315 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
317 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
318 if(eventData->mCursorBlinkEnabled)
320 decorator->StartCursorBlink();
323 if(eventData->mGrabHandleEnabled)
325 decorator->SetHandleActive(GRAB_HANDLE, true);
327 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
328 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
329 decorator->SetHighlightActive(false);
331 if(eventData->mGrabHandlePopupEnabled)
333 impl.SetPopupButtons();
334 decorator->SetPopupActive(true);
336 eventData->mDecoratorUpdated = true;
340 case EventData::TEXT_PANNING:
342 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
343 decorator->StopCursorBlink();
344 decorator->SetHandleActive(GRAB_HANDLE, false);
345 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
346 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
348 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
349 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
350 decorator->SetHighlightActive(true);
353 if(eventData->mGrabHandlePopupEnabled)
355 decorator->SetPopupActive(false);
358 eventData->mDecoratorUpdated = true;
365 } // unnamed Namespace
367 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
368 : mDecorator(decorator),
369 mInputMethodContext(inputMethodContext),
370 mPlaceholderFont(nullptr),
371 mPlaceholderTextActive(),
372 mPlaceholderTextInactive(),
373 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
375 mInputStyleChangedQueue(),
376 mPreviousState(INACTIVE),
378 mPrimaryCursorPosition(0u),
379 mLeftSelectionPosition(0u),
380 mRightSelectionPosition(0u),
381 mPreEditStartPosition(0u),
383 mCursorHookPositionX(0.f),
384 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
385 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
386 mIsShowingPlaceholderText(false),
388 mDecoratorUpdated(false),
389 mCursorBlinkEnabled(true),
390 mGrabHandleEnabled(true),
391 mGrabHandlePopupEnabled(true),
392 mSelectionEnabled(true),
393 mUpdateCursorHookPosition(false),
394 mUpdateCursorPosition(false),
395 mUpdateGrabHandlePosition(false),
396 mUpdateLeftSelectionPosition(false),
397 mUpdateRightSelectionPosition(false),
398 mIsLeftHandleSelected(false),
399 mIsRightHandleSelected(false),
400 mUpdateHighlightBox(false),
401 mScrollAfterUpdatePosition(false),
402 mScrollAfterDelete(false),
403 mAllTextSelected(false),
404 mUpdateInputStyle(false),
405 mPasswordInput(false),
406 mCheckScrollAmount(false),
407 mIsPlaceholderPixelSize(false),
408 mIsPlaceholderElideEnabled(false),
409 mPlaceholderEllipsisFlag(false),
410 mShiftSelectionFlag(true),
411 mUpdateAlignment(false),
412 mEditingEnabled(true)
416 bool Controller::Impl::ProcessInputEvents()
418 return ControllerImplEventHandler::ProcessInputEvents(*this);
421 void Controller::Impl::NotifyInputMethodContext()
423 if(mEventData && mEventData->mInputMethodContext)
425 CharacterIndex cursorPosition = GetLogicalCursorPosition();
427 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
429 // Update the cursor position by removing the initial white spaces.
430 if(cursorPosition < numberOfWhiteSpaces)
436 cursorPosition -= numberOfWhiteSpaces;
439 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
440 mEventData->mInputMethodContext.NotifyCursorPosition();
444 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
446 if(mEventData && mEventData->mInputMethodContext)
448 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
449 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
453 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
455 CharacterIndex cursorPosition = 0u;
459 if((EventData::SELECTING == mEventData->mState) ||
460 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
462 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
466 cursorPosition = mEventData->mPrimaryCursorPosition;
470 return cursorPosition;
473 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
475 Length numberOfWhiteSpaces = 0u;
477 // Get the buffer to the text.
478 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
480 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
481 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
483 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
489 return numberOfWhiteSpaces;
492 void Controller::Impl::GetText(std::string& text) const
494 if(!IsShowingPlaceholderText())
496 // Retrieves the text string.
501 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
505 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
507 // Get the total number of characters.
508 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
510 // Retrieve the text.
511 if(0u != numberOfCharacters)
513 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
517 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
519 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
520 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
522 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
526 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
530 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
532 if(mUpdateTextDirection)
534 // Operations that can be done only once until the text changes.
535 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
543 // Set the update info to relayout the whole text.
544 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
545 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
547 // Make sure the model is up-to-date before layouting
548 UpdateModel(onlyOnceOperations);
551 Relayouter::DoRelayout(*this,
552 Size(MAX_FLOAT, MAX_FLOAT),
553 static_cast<OperationsMask>(onlyOnceOperations |
554 LAYOUT | REORDER | UPDATE_DIRECTION),
555 naturalSize.GetVectorXY());
557 // Do not do again the only once operations.
558 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
560 // Clear the update info. This info will be set the next time the text is updated.
561 mTextUpdateInfo.Clear();
563 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
564 mTextUpdateInfo.mFullRelayoutNeeded = true;
566 mUpdateTextDirection = false;
569 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
572 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
574 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
575 mTextUpdateInfo.mStartGlyphIndex = 0u;
576 mTextUpdateInfo.mStartLineIndex = 0u;
577 numberOfCharacters = 0u;
579 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
580 if(0u == numberOfParagraphs)
582 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
583 numberOfCharacters = 0u;
585 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
587 // Nothing else to do if there are no paragraphs.
591 // Find the paragraphs to be updated.
592 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
593 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
595 // Text is being added at the end of the current text.
596 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
598 // Text is being added in a new paragraph after the last character of the text.
599 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
600 numberOfCharacters = 0u;
601 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
603 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
604 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
606 // Nothing else to do;
610 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
614 Length numberOfCharactersToUpdate = 0u;
615 if(mTextUpdateInfo.mFullRelayoutNeeded)
617 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
621 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
623 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
624 numberOfCharactersToUpdate,
625 paragraphsToBeUpdated);
628 if(0u != paragraphsToBeUpdated.Count())
630 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
631 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
632 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
634 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
635 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
637 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
638 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
639 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
640 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
642 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
643 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
645 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
649 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
653 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
654 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
657 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
659 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
662 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
664 return ControllerImplModelUpdater::Update(*this, operationsRequired);
667 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
669 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
672 float Controller::Impl::GetDefaultFontLineHeight()
674 FontId defaultFontId = 0u;
675 if(nullptr == mFontDefaults)
677 TextAbstraction::FontDescription fontDescription;
678 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
682 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
685 Text::FontMetrics fontMetrics;
686 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
688 return (fontMetrics.ascender - fontMetrics.descender);
691 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
693 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
695 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
697 RelayoutAllCharacters();
703 bool Controller::Impl::SetDefaultLineSize(float lineSize)
705 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
707 mLayoutEngine.SetDefaultLineSize(lineSize);
709 RelayoutAllCharacters();
715 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
717 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
719 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
721 RelayoutAllCharacters();
727 float Controller::Impl::GetRelativeLineSize()
729 return mLayoutEngine.GetRelativeLineSize();
732 string Controller::Impl::GetSelectedText()
735 if(EventData::SELECTING == mEventData->mState)
737 RetrieveSelection(text, false);
742 string Controller::Impl::CopyText()
745 RetrieveSelection(text, false);
746 SendSelectionToClipboard(false); // Text not modified
748 mEventData->mUpdateCursorPosition = true;
750 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
755 string Controller::Impl::CutText()
758 RetrieveSelection(text, false);
765 SendSelectionToClipboard(true); // Synchronous call to modify text
766 mOperationsPending = ALL_OPERATIONS;
768 if((0u != mModel->mLogicalModel->mText.Count()) ||
769 !IsPlaceholderAvailable())
771 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
775 PlaceholderHandler::ShowPlaceholderText(*this);
778 mEventData->mUpdateCursorPosition = true;
779 mEventData->mScrollAfterDelete = true;
783 if(nullptr != mEditableControlInterface)
785 mEditableControlInterface->TextChanged(true);
790 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
792 if(nullptr == mEventData)
794 // Nothing to do if there is no text.
798 if(mEventData->mSelectionEnabled && (pStart || pEnd))
800 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
801 uint32_t oldStart = mEventData->mLeftSelectionPosition;
802 uint32_t oldEnd = mEventData->mRightSelectionPosition;
806 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
810 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
813 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
815 ChangeState(EventData::EDITING);
816 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
817 mEventData->mUpdateCursorPosition = true;
821 ChangeState(EventData::SELECTING);
822 mEventData->mUpdateHighlightBox = true;
823 mEventData->mUpdateLeftSelectionPosition = true;
824 mEventData->mUpdateRightSelectionPosition = true;
827 if(mSelectableControlInterface != nullptr)
829 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
834 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
836 if(nullptr == mEventData)
840 return mEventData->mPrimaryCursorPosition;
843 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
845 if(nullptr == mEventData)
847 // Nothing to do if there is no text.
851 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
853 // Nothing for same cursor position.
857 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
858 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
859 mEventData->mPrimaryCursorPosition = std::min(index, length);
860 // If there is no focus, only the value is updated.
863 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
864 uint32_t oldStart = mEventData->mLeftSelectionPosition;
865 uint32_t oldEnd = mEventData->mRightSelectionPosition;
866 ChangeState(EventData::EDITING);
867 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
868 mEventData->mUpdateCursorPosition = true;
870 if(mSelectableControlInterface != nullptr && wasInSelectingState)
872 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
875 ScrollTextToMatchCursor();
878 if(nullptr != mEditableControlInterface)
880 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
886 Uint32Pair Controller::Impl::GetTextSelectionRange() const
892 range.first = mEventData->mLeftSelectionPosition;
893 range.second = mEventData->mRightSelectionPosition;
899 bool Controller::Impl::IsEditable() const
901 return mEventData && mEventData->mEditingEnabled;
904 void Controller::Impl::SetEditable(bool editable)
908 mEventData->mEditingEnabled = editable;
910 if(mEventData->mDecorator)
912 bool decoratorEditable = editable && mIsUserInteractionEnabled;
913 mEventData->mDecorator->SetEditable(decoratorEditable);
918 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
920 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
922 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
924 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
925 mFontDefaults->mFontDescription.family = newDefaultFont;
933 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
935 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
937 // Nothing to select if handles are in the same place.
938 selectedText.clear();
942 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
944 //Get start and end position of selection
945 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
946 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
948 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
949 const Length numberOfCharacters = utf32Characters.Count();
951 // Validate the start and end selection points
952 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
954 //Get text as a UTF8 string
955 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
957 if(deleteAfterRetrieval) // Only delete text if copied successfully
959 // Keep a copy of the current input style.
960 InputStyle currentInputStyle;
961 currentInputStyle.Copy(mEventData->mInputStyle);
963 // Set as input style the style of the first deleted character.
964 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
966 // Compare if the input style has changed.
967 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
969 if(hasInputStyleChanged)
971 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
972 // Queue the input style changed signal.
973 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
976 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
978 // Mark the paragraphs to be updated.
979 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
981 mTextUpdateInfo.mCharacterIndex = 0;
982 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
983 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
984 mTextUpdateInfo.mClearAll = true;
988 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
989 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
992 // Delete text between handles
993 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
994 Vector<Character>::Iterator last = first + lengthOfSelectedText;
995 utf32Characters.Erase(first, last);
997 // Will show the cursor at the first character of the selection.
998 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1002 // Will show the cursor at the last character of the selection.
1003 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1006 mEventData->mDecoratorUpdated = true;
1010 void Controller::Impl::SetSelection(int start, int end)
1012 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1013 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1015 mEventData->mLeftSelectionPosition = start;
1016 mEventData->mRightSelectionPosition = end;
1017 mEventData->mUpdateCursorPosition = true;
1019 if(mSelectableControlInterface != nullptr)
1021 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1025 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1027 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1030 void Controller::Impl::ShowClipboard()
1034 mClipboard.ShowClipboard();
1038 void Controller::Impl::HideClipboard()
1040 if(mClipboard && mClipboardHideEnabled)
1042 mClipboard.HideClipboard();
1046 void Controller::Impl::SetClipboardHideEnable(bool enable)
1048 mClipboardHideEnabled = enable;
1051 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1053 //Send string to clipboard
1054 return (mClipboard && mClipboard.SetItem(source));
1057 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1059 std::string selectedText;
1060 RetrieveSelection(selectedText, deleteAfterSending);
1061 CopyStringToClipboard(selectedText);
1062 ChangeState(EventData::EDITING);
1065 void Controller::Impl::RequestGetTextFromClipboard()
1069 mClipboard.RequestItem();
1073 void Controller::Impl::RepositionSelectionHandles()
1075 SelectionHandleController::Reposition(*this);
1077 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1079 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1082 void Controller::Impl::SetPopupButtons()
1085 * Sets the Popup buttons to be shown depending on State.
1087 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1089 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1092 bool isEditable = IsEditable();
1093 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1095 if(EventData::SELECTING == mEventData->mState)
1097 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1100 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1103 if(!IsClipboardEmpty())
1107 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1109 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1112 if(!mEventData->mAllTextSelected)
1114 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1117 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1119 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1121 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1124 if(!IsClipboardEmpty())
1128 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1130 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1133 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1135 if(!IsClipboardEmpty())
1139 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1141 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1145 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1148 void Controller::Impl::ChangeState(EventData::State newState)
1150 ChangeTextControllerState(*this, newState);
1153 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1154 CursorInfo& cursorInfo)
1156 if(!IsShowingRealText())
1158 // Do not want to use the place-holder text to set the cursor position.
1160 // Use the line's height of the font's family set to set the cursor's size.
1161 // If there is no font's family set, use the default font.
1162 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1164 cursorInfo.lineOffset = 0.f;
1165 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1166 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1169 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1171 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1174 switch(mModel->mHorizontalAlignment)
1176 case Text::HorizontalAlignment::BEGIN:
1180 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1184 cursorInfo.primaryPosition.x = 0.f;
1188 case Text::HorizontalAlignment::CENTER:
1190 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1193 case Text::HorizontalAlignment::END:
1197 cursorInfo.primaryPosition.x = 0.f;
1201 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1207 // Nothing else to do.
1211 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1212 GetCursorPositionParameters parameters;
1213 parameters.visualModel = mModel->mVisualModel;
1214 parameters.logicalModel = mModel->mLogicalModel;
1215 parameters.metrics = mMetrics;
1216 parameters.logical = logical;
1217 parameters.isMultiline = isMultiLine;
1219 float defaultFontLineHeight = GetDefaultFontLineHeight();
1221 Text::GetCursorPosition(parameters,
1222 defaultFontLineHeight,
1225 // Adds Outline offset.
1226 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1227 cursorInfo.primaryPosition.x += outlineWidth;
1228 cursorInfo.primaryPosition.y += outlineWidth;
1229 cursorInfo.secondaryPosition.x += outlineWidth;
1230 cursorInfo.secondaryPosition.y += outlineWidth;
1234 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1236 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1237 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1239 if(0.f > cursorInfo.primaryPosition.x)
1241 cursorInfo.primaryPosition.x = 0.f;
1244 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1245 if(cursorInfo.primaryPosition.x > edgeWidth)
1247 cursorInfo.primaryPosition.x = edgeWidth;
1252 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1254 if(nullptr == mEventData)
1256 // Nothing to do if there is no text input.
1260 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1262 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1263 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1265 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1266 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1268 if(numberOfCharacters > 1u)
1270 const Script script = mModel->mLogicalModel->GetScript(index);
1271 if(HasLigatureMustBreak(script))
1273 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1274 numberOfCharacters = 1u;
1279 while(0u == numberOfCharacters)
1282 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1286 if(index < mEventData->mPrimaryCursorPosition)
1288 cursorIndex -= numberOfCharacters;
1292 cursorIndex += numberOfCharacters;
1295 // Will update the cursor hook position.
1296 mEventData->mUpdateCursorHookPosition = true;
1301 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1303 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1304 if(nullptr == mEventData)
1306 // Nothing to do if there is no text input.
1307 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1311 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1313 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1315 // Sets the cursor position.
1316 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1319 cursorInfo.primaryCursorHeight,
1320 cursorInfo.lineHeight);
1321 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1323 if(mEventData->mUpdateGrabHandlePosition)
1325 // Sets the grab handle position.
1326 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1328 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1329 cursorInfo.lineHeight);
1332 if(cursorInfo.isSecondaryCursor)
1334 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1335 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1336 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1337 cursorInfo.secondaryCursorHeight,
1338 cursorInfo.lineHeight);
1339 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1342 // Set which cursors are active according the state.
1343 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1345 if(cursorInfo.isSecondaryCursor)
1347 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1351 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1356 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1359 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1362 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1363 const CursorInfo& cursorInfo)
1365 SelectionHandleController::Update(*this, handleType, cursorInfo);
1368 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1370 // Clamp between -space & -alignment offset.
1372 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1374 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1375 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1376 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1378 mEventData->mDecoratorUpdated = true;
1382 mModel->mScrollPosition.x = 0.f;
1386 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1388 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1390 // Nothing to do if the text is single line.
1394 // Clamp between -space & 0.
1395 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1397 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1398 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1399 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1401 mEventData->mDecoratorUpdated = true;
1405 mModel->mScrollPosition.y = 0.f;
1409 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1411 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1413 // position is in actor's coords.
1414 const float positionEndX = position.x + cursorWidth;
1415 const float positionEndY = position.y + lineHeight;
1417 // Transform the position to decorator coords.
1418 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1419 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1421 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1422 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1424 if(decoratorPositionBeginX < 0.f)
1426 mModel->mScrollPosition.x = -position.x;
1428 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1430 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1433 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1435 if(decoratorPositionBeginY < 0.f)
1437 mModel->mScrollPosition.y = -position.y;
1439 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1441 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1446 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1448 // Get the current cursor position in decorator coords.
1449 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1451 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1453 // Calculate the offset to match the cursor position before the character was deleted.
1454 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1456 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1457 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1459 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1460 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1463 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1464 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1466 // Makes the new cursor position visible if needed.
1467 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1470 void Controller::Impl::ScrollTextToMatchCursor()
1472 CursorInfo cursorInfo;
1473 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1474 ScrollTextToMatchCursor(cursorInfo);
1477 void Controller::Impl::RequestRelayout()
1479 if(nullptr != mControlInterface)
1481 mControlInterface->RequestTextRelayout();
1485 void Controller::Impl::RelayoutAllCharacters()
1487 // relayout all characters
1488 mTextUpdateInfo.mCharacterIndex = 0;
1489 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1490 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1491 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1493 mTextUpdateInfo.mFullRelayoutNeeded = true;
1495 // Need to recalculate natural size
1496 mRecalculateNaturalSize = true;
1499 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1501 ChangeState(EventData::EDITING);
1507 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1509 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1512 void Controller::Impl::ProcessInputStyleChangedSignals()
1516 if(mEditableControlInterface)
1518 // Emit the input style changed signal for each mask
1519 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1520 mEventData->mInputStyleChangedQueue.end(),
1521 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1524 mEventData->mInputStyleChangedQueue.Clear();
1528 void Controller::Impl::ScrollBy(Vector2 scroll)
1530 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1532 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1533 const Vector2 currentScroll = mModel->mScrollPosition;
1535 scroll.x = -scroll.x;
1536 scroll.y = -scroll.y;
1538 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1540 mModel->mScrollPosition.x += scroll.x;
1541 ClampHorizontalScroll(layoutSize);
1544 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1546 mModel->mScrollPosition.y += scroll.y;
1547 ClampVerticalScroll(layoutSize);
1550 if(mModel->mScrollPosition != currentScroll)
1552 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1558 float Controller::Impl::GetHorizontalScrollPosition()
1560 // Scroll values are negative internally so we convert them to positive numbers
1561 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1564 float Controller::Impl::GetVerticalScrollPosition()
1566 // Scroll values are negative internally so we convert them to positive numbers
1567 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1570 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1573 return Vector3(10.f, 10.f, 10.f);
1576 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1579 return Vector2(10.f, 10.f);
1582 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1584 auto actor = Toolkit::TextAnchor::New();
1585 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1586 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1587 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1588 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1589 const Vector2 anchorSize = GetAnchorSize(anchor);
1590 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1591 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1592 actor.SetProperty(Actor::Property::NAME, anchorText);
1593 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1594 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1595 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1599 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1601 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1602 we need to create and destroy potentially many actors. Some optimization can be considered here.
1603 Maybe a "dirty" flag in mLogicalModel? */
1604 anchorActors.clear();
1605 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1607 auto actor = CreateAnchorActor(anchor);
1608 anchorActors.push_back(actor);
1612 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1614 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1616 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1621 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1624 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1626 //Underlined character runs for markup-processor
1627 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1628 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1629 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1631 if(shouldClearPreUnderlineRuns)
1633 mModel->mVisualModel->mUnderlineRuns.Clear();
1636 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1638 CharacterIndex characterIndex = it->characterRun.characterIndex;
1639 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1641 if(numberOfCharacters == 0)
1646 // Create one run for all glyphs of all run's characters that has same properties
1647 // This enhance performance and reduce the needed memory to store glyphs-runs
1648 UnderlinedGlyphRun underlineGlyphRun;
1649 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1650 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1651 //Copy properties (attributes)
1652 underlineGlyphRun.properties = it->properties;
1654 for(Length index = 1u; index < numberOfCharacters; index++)
1656 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1659 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1663 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1665 //Strikethrough character runs from markup-processor
1666 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1667 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1668 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1670 mModel->mVisualModel->mStrikethroughRuns.Clear();
1672 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1674 CharacterIndex characterIndex = it->characterRun.characterIndex;
1675 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1677 if(numberOfCharacters == 0)
1682 StrikethroughGlyphRun strikethroughGlyphRun;
1683 strikethroughGlyphRun.properties = it->properties;
1684 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1685 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1687 for(Length index = 1u; index < numberOfCharacters; index++)
1689 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1692 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1696 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1698 //CharacterSpacing character runs from markup-processor
1699 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1700 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1701 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1703 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1705 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1707 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1708 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1710 if(numberOfCharacters == 0)
1715 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1716 characterSpacingGlyphRun.value = it->value;
1717 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1718 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1720 for(Length index = 1u; index < numberOfCharacters; index++)
1722 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1725 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1729 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1731 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1733 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1736 UPDATE_LAYOUT_SIZE |
1741 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1742 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1746 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1749 mIsAutoScrollEnabled = enable;
1754 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1755 mIsAutoScrollEnabled = false;
1759 void Controller::Impl::SetEnableCursorBlink(bool enable)
1761 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1765 mEventData->mCursorBlinkEnabled = enable;
1767 if(!enable && mEventData->mDecorator)
1769 mEventData->mDecorator->StopCursorBlink();
1774 void Controller::Impl::SetMultiLineEnabled(bool enable)
1776 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1778 if(layout != mLayoutEngine.GetLayout())
1780 // Set the layout type.
1781 mLayoutEngine.SetLayout(layout);
1783 // Set the flags to redo the layout operations
1784 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1785 UPDATE_LAYOUT_SIZE |
1789 mTextUpdateInfo.mFullRelayoutNeeded = true;
1790 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1792 // Need to recalculate natural size
1793 mRecalculateNaturalSize = true;
1799 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1801 if(alignment != mModel->mHorizontalAlignment)
1803 // Set the alignment.
1804 mModel->mHorizontalAlignment = alignment;
1806 // Set the flag to redo the alignment operation.
1807 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1811 mEventData->mUpdateAlignment = true;
1813 // Update the cursor if it's in editing mode
1814 if(EventData::IsEditingState(mEventData->mState))
1816 ChangeState(EventData::EDITING);
1817 mEventData->mUpdateCursorPosition = true;
1825 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1827 if(alignment != mModel->mVerticalAlignment)
1829 // Set the alignment.
1830 mModel->mVerticalAlignment = alignment;
1831 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1836 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1838 if(lineWrapMode != mModel->mLineWrapMode)
1840 // Update Text layout for applying wrap mode
1841 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1844 UPDATE_LAYOUT_SIZE |
1847 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1848 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1850 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1853 // Set the text wrap mode.
1854 mModel->mLineWrapMode = lineWrapMode;
1856 mTextUpdateInfo.mCharacterIndex = 0u;
1857 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1858 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1865 void Controller::Impl::SetDefaultColor(const Vector4& color)
1869 if(!IsShowingPlaceholderText())
1871 mModel->mVisualModel->SetTextColor(color);
1872 mModel->mLogicalModel->mColorRuns.Clear();
1873 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1878 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1880 mIsUserInteractionEnabled = enabled;
1882 if(mEventData && mEventData->mDecorator)
1884 bool editable = mEventData->mEditingEnabled && enabled;
1885 mEventData->mDecorator->SetEditable(editable);
1889 void Controller::Impl::ClearFontData()
1893 mFontDefaults->mFontId = 0u; // Remove old font ID
1896 // Set flags to update the model.
1897 mTextUpdateInfo.mCharacterIndex = 0u;
1898 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1899 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1901 mTextUpdateInfo.mClearAll = true;
1902 mTextUpdateInfo.mFullRelayoutNeeded = true;
1903 mRecalculateNaturalSize = true;
1905 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1911 UPDATE_LAYOUT_SIZE |
1916 void Controller::Impl::ClearStyleData()
1918 mModel->mLogicalModel->mColorRuns.Clear();
1919 mModel->mLogicalModel->ClearFontDescriptionRuns();
1920 mModel->mLogicalModel->ClearStrikethroughRuns();
1923 void Controller::Impl::ResetScrollPosition()
1927 // Reset the scroll position.
1928 mModel->mScrollPosition = Vector2::ZERO;
1929 mEventData->mScrollAfterUpdatePosition = true;
1933 } // namespace Dali::Toolkit::Text