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 mEventData->mDecorator->SetEditable(editable);
917 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
919 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
921 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
923 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
924 mFontDefaults->mFontDescription.family = newDefaultFont;
932 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
934 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
936 // Nothing to select if handles are in the same place.
937 selectedText.clear();
941 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
943 //Get start and end position of selection
944 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
945 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
947 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
948 const Length numberOfCharacters = utf32Characters.Count();
950 // Validate the start and end selection points
951 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
953 //Get text as a UTF8 string
954 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
956 if(deleteAfterRetrieval) // Only delete text if copied successfully
958 // Keep a copy of the current input style.
959 InputStyle currentInputStyle;
960 currentInputStyle.Copy(mEventData->mInputStyle);
962 // Set as input style the style of the first deleted character.
963 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
965 // Compare if the input style has changed.
966 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
968 if(hasInputStyleChanged)
970 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
971 // Queue the input style changed signal.
972 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
975 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
977 // Mark the paragraphs to be updated.
978 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
980 mTextUpdateInfo.mCharacterIndex = 0;
981 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
982 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
983 mTextUpdateInfo.mClearAll = true;
987 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
988 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
991 // Delete text between handles
992 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
993 Vector<Character>::Iterator last = first + lengthOfSelectedText;
994 utf32Characters.Erase(first, last);
996 // Will show the cursor at the first character of the selection.
997 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1001 // Will show the cursor at the last character of the selection.
1002 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1005 mEventData->mDecoratorUpdated = true;
1009 void Controller::Impl::SetSelection(int start, int end)
1011 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1012 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1014 mEventData->mLeftSelectionPosition = start;
1015 mEventData->mRightSelectionPosition = end;
1016 mEventData->mUpdateCursorPosition = true;
1018 if(mSelectableControlInterface != nullptr)
1020 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1024 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1026 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1029 void Controller::Impl::ShowClipboard()
1033 mClipboard.ShowClipboard();
1037 void Controller::Impl::HideClipboard()
1039 if(mClipboard && mClipboardHideEnabled)
1041 mClipboard.HideClipboard();
1045 void Controller::Impl::SetClipboardHideEnable(bool enable)
1047 mClipboardHideEnabled = enable;
1050 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1052 //Send string to clipboard
1053 return (mClipboard && mClipboard.SetItem(source));
1056 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1058 std::string selectedText;
1059 RetrieveSelection(selectedText, deleteAfterSending);
1060 CopyStringToClipboard(selectedText);
1061 ChangeState(EventData::EDITING);
1064 void Controller::Impl::RequestGetTextFromClipboard()
1068 mClipboard.RequestItem();
1072 void Controller::Impl::RepositionSelectionHandles()
1074 SelectionHandleController::Reposition(*this);
1076 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1078 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1081 void Controller::Impl::SetPopupButtons()
1084 * Sets the Popup buttons to be shown depending on State.
1086 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1088 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1091 bool isEditable = IsEditable();
1092 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1094 if(EventData::SELECTING == mEventData->mState)
1096 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1099 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1102 if(!IsClipboardEmpty())
1106 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1108 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1111 if(!mEventData->mAllTextSelected)
1113 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1116 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1118 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1120 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1123 if(!IsClipboardEmpty())
1127 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1129 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1132 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1134 if(!IsClipboardEmpty())
1138 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1140 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1144 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1147 void Controller::Impl::ChangeState(EventData::State newState)
1149 ChangeTextControllerState(*this, newState);
1152 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1153 CursorInfo& cursorInfo)
1155 if(!IsShowingRealText())
1157 // Do not want to use the place-holder text to set the cursor position.
1159 // Use the line's height of the font's family set to set the cursor's size.
1160 // If there is no font's family set, use the default font.
1161 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1163 cursorInfo.lineOffset = 0.f;
1164 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1165 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1168 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1170 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1173 switch(mModel->mHorizontalAlignment)
1175 case Text::HorizontalAlignment::BEGIN:
1179 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1183 cursorInfo.primaryPosition.x = 0.f;
1187 case Text::HorizontalAlignment::CENTER:
1189 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1192 case Text::HorizontalAlignment::END:
1196 cursorInfo.primaryPosition.x = 0.f;
1200 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1206 // Nothing else to do.
1210 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1211 GetCursorPositionParameters parameters;
1212 parameters.visualModel = mModel->mVisualModel;
1213 parameters.logicalModel = mModel->mLogicalModel;
1214 parameters.metrics = mMetrics;
1215 parameters.logical = logical;
1216 parameters.isMultiline = isMultiLine;
1218 float defaultFontLineHeight = GetDefaultFontLineHeight();
1220 Text::GetCursorPosition(parameters,
1221 defaultFontLineHeight,
1224 // Adds Outline offset.
1225 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1226 cursorInfo.primaryPosition.x += outlineWidth;
1227 cursorInfo.primaryPosition.y += outlineWidth;
1228 cursorInfo.secondaryPosition.x += outlineWidth;
1229 cursorInfo.secondaryPosition.y += outlineWidth;
1233 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1235 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1236 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1238 if(0.f > cursorInfo.primaryPosition.x)
1240 cursorInfo.primaryPosition.x = 0.f;
1243 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1244 if(cursorInfo.primaryPosition.x > edgeWidth)
1246 cursorInfo.primaryPosition.x = edgeWidth;
1251 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1253 if(nullptr == mEventData)
1255 // Nothing to do if there is no text input.
1259 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1261 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1262 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1264 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1265 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1267 if(numberOfCharacters > 1u)
1269 const Script script = mModel->mLogicalModel->GetScript(index);
1270 if(HasLigatureMustBreak(script))
1272 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1273 numberOfCharacters = 1u;
1278 while(0u == numberOfCharacters)
1281 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1285 if(index < mEventData->mPrimaryCursorPosition)
1287 cursorIndex -= numberOfCharacters;
1291 cursorIndex += numberOfCharacters;
1294 // Will update the cursor hook position.
1295 mEventData->mUpdateCursorHookPosition = true;
1300 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1302 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1303 if(nullptr == mEventData)
1305 // Nothing to do if there is no text input.
1306 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1310 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1312 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1314 // Sets the cursor position.
1315 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1318 cursorInfo.primaryCursorHeight,
1319 cursorInfo.lineHeight);
1320 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1322 if(mEventData->mUpdateGrabHandlePosition)
1324 // Sets the grab handle position.
1325 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1327 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1328 cursorInfo.lineHeight);
1331 if(cursorInfo.isSecondaryCursor)
1333 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1334 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1335 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1336 cursorInfo.secondaryCursorHeight,
1337 cursorInfo.lineHeight);
1338 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1341 // Set which cursors are active according the state.
1342 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1344 if(cursorInfo.isSecondaryCursor)
1346 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1350 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1355 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1358 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1361 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1362 const CursorInfo& cursorInfo)
1364 SelectionHandleController::Update(*this, handleType, cursorInfo);
1367 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1369 // Clamp between -space & -alignment offset.
1371 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1373 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1374 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1375 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1377 mEventData->mDecoratorUpdated = true;
1381 mModel->mScrollPosition.x = 0.f;
1385 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1387 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1389 // Nothing to do if the text is single line.
1393 // Clamp between -space & 0.
1394 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1396 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1397 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1398 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1400 mEventData->mDecoratorUpdated = true;
1404 mModel->mScrollPosition.y = 0.f;
1408 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1410 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1412 // position is in actor's coords.
1413 const float positionEndX = position.x + cursorWidth;
1414 const float positionEndY = position.y + lineHeight;
1416 // Transform the position to decorator coords.
1417 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1418 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1420 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1421 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1423 if(decoratorPositionBeginX < 0.f)
1425 mModel->mScrollPosition.x = -position.x;
1427 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1429 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1432 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1434 if(decoratorPositionBeginY < 0.f)
1436 mModel->mScrollPosition.y = -position.y;
1438 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1440 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1445 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1447 // Get the current cursor position in decorator coords.
1448 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1450 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1452 // Calculate the offset to match the cursor position before the character was deleted.
1453 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1455 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1456 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1458 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1459 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1462 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1463 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1465 // Makes the new cursor position visible if needed.
1466 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1469 void Controller::Impl::ScrollTextToMatchCursor()
1471 CursorInfo cursorInfo;
1472 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1473 ScrollTextToMatchCursor(cursorInfo);
1476 void Controller::Impl::RequestRelayout()
1478 if(nullptr != mControlInterface)
1480 mControlInterface->RequestTextRelayout();
1484 void Controller::Impl::RelayoutAllCharacters()
1486 // relayout all characters
1487 mTextUpdateInfo.mCharacterIndex = 0;
1488 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1489 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1490 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1492 mTextUpdateInfo.mFullRelayoutNeeded = true;
1494 // Need to recalculate natural size
1495 mRecalculateNaturalSize = true;
1498 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1500 ChangeState(EventData::EDITING);
1506 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1508 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1511 void Controller::Impl::ProcessInputStyleChangedSignals()
1515 if(mEditableControlInterface)
1517 // Emit the input style changed signal for each mask
1518 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1519 mEventData->mInputStyleChangedQueue.end(),
1520 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1523 mEventData->mInputStyleChangedQueue.Clear();
1527 void Controller::Impl::ScrollBy(Vector2 scroll)
1529 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1531 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1532 const Vector2 currentScroll = mModel->mScrollPosition;
1534 scroll.x = -scroll.x;
1535 scroll.y = -scroll.y;
1537 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1539 mModel->mScrollPosition.x += scroll.x;
1540 ClampHorizontalScroll(layoutSize);
1543 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1545 mModel->mScrollPosition.y += scroll.y;
1546 ClampVerticalScroll(layoutSize);
1549 if(mModel->mScrollPosition != currentScroll)
1551 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1557 float Controller::Impl::GetHorizontalScrollPosition()
1559 // Scroll values are negative internally so we convert them to positive numbers
1560 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1563 float Controller::Impl::GetVerticalScrollPosition()
1565 // Scroll values are negative internally so we convert them to positive numbers
1566 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1569 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1572 return Vector3(10.f, 10.f, 10.f);
1575 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1578 return Vector2(10.f, 10.f);
1581 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1583 auto actor = Toolkit::TextAnchor::New();
1584 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1585 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1586 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1587 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1588 const Vector2 anchorSize = GetAnchorSize(anchor);
1589 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1590 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1591 actor.SetProperty(Actor::Property::NAME, anchorText);
1592 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1593 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1594 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1598 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1600 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1601 we need to create and destroy potentially many actors. Some optimization can be considered here.
1602 Maybe a "dirty" flag in mLogicalModel? */
1603 anchorActors.clear();
1604 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1606 auto actor = CreateAnchorActor(anchor);
1607 anchorActors.push_back(actor);
1611 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1613 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1615 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1620 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1623 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1625 //Underlined character runs for markup-processor
1626 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1627 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1628 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1630 if(shouldClearPreUnderlineRuns)
1632 mModel->mVisualModel->mUnderlineRuns.Clear();
1635 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1637 CharacterIndex characterIndex = it->characterRun.characterIndex;
1638 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1639 for(Length index = 0u; index < numberOfCharacters; index++)
1641 UnderlinedGlyphRun underlineGlyphRun;
1642 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1643 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1645 //Copy properties (attributes)
1646 underlineGlyphRun.properties.type = it->properties.type;
1647 underlineGlyphRun.properties.color = it->properties.color;
1648 underlineGlyphRun.properties.height = it->properties.height;
1649 underlineGlyphRun.properties.dashGap = it->properties.dashGap;
1650 underlineGlyphRun.properties.dashWidth = it->properties.dashWidth;
1651 underlineGlyphRun.properties.typeDefined = it->properties.typeDefined;
1652 underlineGlyphRun.properties.colorDefined = it->properties.colorDefined;
1653 underlineGlyphRun.properties.heightDefined = it->properties.heightDefined;
1654 underlineGlyphRun.properties.dashGapDefined = it->properties.dashGapDefined;
1655 underlineGlyphRun.properties.dashWidthDefined = it->properties.dashWidthDefined;
1657 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1662 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1664 //Strikethrough character runs from markup-processor
1665 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1666 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1667 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1669 mModel->mVisualModel->mStrikethroughRuns.Clear();
1671 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1673 CharacterIndex characterIndex = it->characterRun.characterIndex;
1674 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1676 if(numberOfCharacters == 0)
1681 StrikethroughGlyphRun strikethroughGlyphRun;
1682 strikethroughGlyphRun.color = it->color;
1683 strikethroughGlyphRun.isColorSet = it->isColorSet;
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::SetAutoScrollEnabled(bool enable)
1698 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1700 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1703 UPDATE_LAYOUT_SIZE |
1708 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1709 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1713 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1716 mIsAutoScrollEnabled = enable;
1721 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1722 mIsAutoScrollEnabled = false;
1726 void Controller::Impl::SetEnableCursorBlink(bool enable)
1728 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1732 mEventData->mCursorBlinkEnabled = enable;
1734 if(!enable && mEventData->mDecorator)
1736 mEventData->mDecorator->StopCursorBlink();
1741 void Controller::Impl::SetMultiLineEnabled(bool enable)
1743 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1745 if(layout != mLayoutEngine.GetLayout())
1747 // Set the layout type.
1748 mLayoutEngine.SetLayout(layout);
1750 // Set the flags to redo the layout operations
1751 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1752 UPDATE_LAYOUT_SIZE |
1756 mTextUpdateInfo.mFullRelayoutNeeded = true;
1757 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1759 // Need to recalculate natural size
1760 mRecalculateNaturalSize = true;
1766 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1768 if(alignment != mModel->mHorizontalAlignment)
1770 // Set the alignment.
1771 mModel->mHorizontalAlignment = alignment;
1773 // Set the flag to redo the alignment operation.
1774 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1778 mEventData->mUpdateAlignment = true;
1780 // Update the cursor if it's in editing mode
1781 if(EventData::IsEditingState(mEventData->mState))
1783 ChangeState(EventData::EDITING);
1784 mEventData->mUpdateCursorPosition = true;
1792 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1794 if(alignment != mModel->mVerticalAlignment)
1796 // Set the alignment.
1797 mModel->mVerticalAlignment = alignment;
1798 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1803 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1805 if(lineWrapMode != mModel->mLineWrapMode)
1807 // Update Text layout for applying wrap mode
1808 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1811 UPDATE_LAYOUT_SIZE |
1814 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1815 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1817 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1820 // Set the text wrap mode.
1821 mModel->mLineWrapMode = lineWrapMode;
1823 mTextUpdateInfo.mCharacterIndex = 0u;
1824 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1825 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1832 void Controller::Impl::SetDefaultColor(const Vector4& color)
1836 if(!IsShowingPlaceholderText())
1838 mModel->mVisualModel->SetTextColor(color);
1839 mModel->mLogicalModel->mColorRuns.Clear();
1840 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1845 void Controller::Impl::ClearFontData()
1849 mFontDefaults->mFontId = 0u; // Remove old font ID
1852 // Set flags to update the model.
1853 mTextUpdateInfo.mCharacterIndex = 0u;
1854 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1855 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1857 mTextUpdateInfo.mClearAll = true;
1858 mTextUpdateInfo.mFullRelayoutNeeded = true;
1859 mRecalculateNaturalSize = true;
1861 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1867 UPDATE_LAYOUT_SIZE |
1872 void Controller::Impl::ClearStyleData()
1874 mModel->mLogicalModel->mColorRuns.Clear();
1875 mModel->mLogicalModel->ClearFontDescriptionRuns();
1876 mModel->mLogicalModel->ClearStrikethroughRuns();
1879 void Controller::Impl::ResetScrollPosition()
1883 // Reset the scroll position.
1884 mModel->mScrollPosition = Vector2::ZERO;
1885 mEventData->mScrollAfterUpdatePosition = true;
1889 } // namespace Dali::Toolkit::Text