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 void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment)
367 EventData* eventData = impl.mEventData;
369 // Set the flag to redo the alignment operation
370 impl.mOperationsPending = static_cast<Controller::OperationsMask>(impl.mOperationsPending | Controller::OperationsMask::ALIGN);
374 // Note: mUpdateAlignment is currently only needed for horizontal alignment
375 eventData->mUpdateAlignment = needFullAlignment;
377 // Update the cursor if it's in editing mode
378 if(EventData::IsEditingState(eventData->mState))
380 impl.ChangeState(EventData::EDITING);
381 eventData->mUpdateCursorPosition = true;
386 } // unnamed Namespace
388 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
389 : mDecorator(decorator),
390 mInputMethodContext(inputMethodContext),
391 mPlaceholderFont(nullptr),
392 mPlaceholderTextActive(),
393 mPlaceholderTextInactive(),
394 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
396 mInputStyleChangedQueue(),
397 mPreviousState(INACTIVE),
399 mPrimaryCursorPosition(0u),
400 mLeftSelectionPosition(0u),
401 mRightSelectionPosition(0u),
402 mPreEditStartPosition(0u),
404 mCursorHookPositionX(0.f),
405 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
406 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
407 mIsShowingPlaceholderText(false),
409 mDecoratorUpdated(false),
410 mCursorBlinkEnabled(true),
411 mGrabHandleEnabled(true),
412 mGrabHandlePopupEnabled(true),
413 mSelectionEnabled(true),
414 mUpdateCursorHookPosition(false),
415 mUpdateCursorPosition(false),
416 mUpdateGrabHandlePosition(false),
417 mUpdateLeftSelectionPosition(false),
418 mUpdateRightSelectionPosition(false),
419 mIsLeftHandleSelected(false),
420 mIsRightHandleSelected(false),
421 mUpdateHighlightBox(false),
422 mScrollAfterUpdatePosition(false),
423 mScrollAfterDelete(false),
424 mAllTextSelected(false),
425 mUpdateInputStyle(false),
426 mPasswordInput(false),
427 mCheckScrollAmount(false),
428 mIsPlaceholderPixelSize(false),
429 mIsPlaceholderElideEnabled(false),
430 mPlaceholderEllipsisFlag(false),
431 mShiftSelectionFlag(true),
432 mUpdateAlignment(false),
433 mEditingEnabled(true)
437 bool Controller::Impl::ProcessInputEvents()
439 return ControllerImplEventHandler::ProcessInputEvents(*this);
442 void Controller::Impl::NotifyInputMethodContext()
444 if(mEventData && mEventData->mInputMethodContext)
446 CharacterIndex cursorPosition = GetLogicalCursorPosition();
448 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
450 // Update the cursor position by removing the initial white spaces.
451 if(cursorPosition < numberOfWhiteSpaces)
457 cursorPosition -= numberOfWhiteSpaces;
460 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
461 mEventData->mInputMethodContext.NotifyCursorPosition();
465 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
467 if(mEventData && mEventData->mInputMethodContext)
469 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
470 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
474 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
476 CharacterIndex cursorPosition = 0u;
480 if((EventData::SELECTING == mEventData->mState) ||
481 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
483 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
487 cursorPosition = mEventData->mPrimaryCursorPosition;
491 return cursorPosition;
494 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
496 Length numberOfWhiteSpaces = 0u;
498 // Get the buffer to the text.
499 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
501 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
502 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
504 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
510 return numberOfWhiteSpaces;
513 void Controller::Impl::GetText(std::string& text) const
515 if(!IsShowingPlaceholderText())
517 // Retrieves the text string.
522 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
526 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
528 // Get the total number of characters.
529 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
531 // Retrieve the text.
532 if(0u != numberOfCharacters)
534 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
538 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
540 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
541 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
543 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
547 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
551 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
553 if(mUpdateTextDirection)
555 // Operations that can be done only once until the text changes.
556 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
564 // Set the update info to relayout the whole text.
565 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
566 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
568 // Make sure the model is up-to-date before layouting
569 UpdateModel(onlyOnceOperations);
572 Relayouter::DoRelayout(*this,
573 Size(MAX_FLOAT, MAX_FLOAT),
574 static_cast<OperationsMask>(onlyOnceOperations |
575 LAYOUT | REORDER | UPDATE_DIRECTION),
576 naturalSize.GetVectorXY());
578 // Do not do again the only once operations.
579 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
581 // Clear the update info. This info will be set the next time the text is updated.
582 mTextUpdateInfo.Clear();
584 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
585 mTextUpdateInfo.mFullRelayoutNeeded = true;
587 mUpdateTextDirection = false;
590 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
593 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
595 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
596 mTextUpdateInfo.mStartGlyphIndex = 0u;
597 mTextUpdateInfo.mStartLineIndex = 0u;
598 numberOfCharacters = 0u;
600 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
601 if(0u == numberOfParagraphs)
603 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
604 numberOfCharacters = 0u;
606 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
608 // Nothing else to do if there are no paragraphs.
612 // Find the paragraphs to be updated.
613 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
614 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
616 // Text is being added at the end of the current text.
617 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
619 // Text is being added in a new paragraph after the last character of the text.
620 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
621 numberOfCharacters = 0u;
622 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
624 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
625 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
627 // Nothing else to do;
631 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
635 Length numberOfCharactersToUpdate = 0u;
636 if(mTextUpdateInfo.mFullRelayoutNeeded)
638 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
642 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
644 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
645 numberOfCharactersToUpdate,
646 paragraphsToBeUpdated);
649 if(0u != paragraphsToBeUpdated.Count())
651 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
652 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
653 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
655 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
656 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
658 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
659 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
660 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
661 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
663 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
664 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
666 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
670 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
674 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
675 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
678 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
680 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
683 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
685 return ControllerImplModelUpdater::Update(*this, operationsRequired);
688 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
690 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
693 float Controller::Impl::GetDefaultFontLineHeight()
695 FontId defaultFontId = 0u;
696 if(nullptr == mFontDefaults)
698 TextAbstraction::FontDescription fontDescription;
699 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
703 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
706 Text::FontMetrics fontMetrics;
707 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
709 return (fontMetrics.ascender - fontMetrics.descender);
712 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
714 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
716 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
718 RelayoutAllCharacters();
724 bool Controller::Impl::SetDefaultLineSize(float lineSize)
726 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
728 mLayoutEngine.SetDefaultLineSize(lineSize);
730 RelayoutAllCharacters();
736 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
738 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
740 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
742 RelayoutAllCharacters();
748 float Controller::Impl::GetRelativeLineSize()
750 return mLayoutEngine.GetRelativeLineSize();
753 string Controller::Impl::GetSelectedText()
756 if(EventData::SELECTING == mEventData->mState)
758 RetrieveSelection(text, false);
763 string Controller::Impl::CopyText()
766 RetrieveSelection(text, false);
767 SendSelectionToClipboard(false); // Text not modified
769 mEventData->mUpdateCursorPosition = true;
771 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
776 string Controller::Impl::CutText()
779 RetrieveSelection(text, false);
786 SendSelectionToClipboard(true); // Synchronous call to modify text
787 mOperationsPending = ALL_OPERATIONS;
789 if((0u != mModel->mLogicalModel->mText.Count()) ||
790 !IsPlaceholderAvailable())
792 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
796 PlaceholderHandler::ShowPlaceholderText(*this);
799 mEventData->mUpdateCursorPosition = true;
800 mEventData->mScrollAfterDelete = true;
804 if(nullptr != mEditableControlInterface)
806 mEditableControlInterface->TextChanged(true);
811 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
813 if(nullptr == mEventData)
815 // Nothing to do if there is no text.
819 if(mEventData->mSelectionEnabled && (pStart || pEnd))
821 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
822 uint32_t oldStart = mEventData->mLeftSelectionPosition;
823 uint32_t oldEnd = mEventData->mRightSelectionPosition;
827 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
831 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
834 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
836 ChangeState(EventData::EDITING);
837 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
838 mEventData->mUpdateCursorPosition = true;
842 ChangeState(EventData::SELECTING);
843 mEventData->mUpdateHighlightBox = true;
844 mEventData->mUpdateLeftSelectionPosition = true;
845 mEventData->mUpdateRightSelectionPosition = true;
848 if(mSelectableControlInterface != nullptr)
850 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
855 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
857 if(nullptr == mEventData)
861 return mEventData->mPrimaryCursorPosition;
864 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
866 if(nullptr == mEventData)
868 // Nothing to do if there is no text.
872 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
874 // Nothing for same cursor position.
878 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
879 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
880 mEventData->mPrimaryCursorPosition = std::min(index, length);
881 // If there is no focus, only the value is updated.
884 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
885 uint32_t oldStart = mEventData->mLeftSelectionPosition;
886 uint32_t oldEnd = mEventData->mRightSelectionPosition;
887 ChangeState(EventData::EDITING);
888 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
889 mEventData->mUpdateCursorPosition = true;
891 if(mSelectableControlInterface != nullptr && wasInSelectingState)
893 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
896 ScrollTextToMatchCursor();
899 if(nullptr != mEditableControlInterface)
901 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
907 Uint32Pair Controller::Impl::GetTextSelectionRange() const
913 range.first = mEventData->mLeftSelectionPosition;
914 range.second = mEventData->mRightSelectionPosition;
920 bool Controller::Impl::IsEditable() const
922 return mEventData && mEventData->mEditingEnabled;
925 void Controller::Impl::SetEditable(bool editable)
929 mEventData->mEditingEnabled = editable;
931 if(mEventData->mDecorator)
933 bool decoratorEditable = editable && mIsUserInteractionEnabled;
934 mEventData->mDecorator->SetEditable(decoratorEditable);
939 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
941 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
943 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
945 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
946 mFontDefaults->mFontDescription.family = newDefaultFont;
954 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
956 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
958 // Nothing to select if handles are in the same place.
959 selectedText.clear();
963 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
965 //Get start and end position of selection
966 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
967 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
969 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
970 const Length numberOfCharacters = utf32Characters.Count();
972 // Validate the start and end selection points
973 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
975 //Get text as a UTF8 string
976 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
978 if(deleteAfterRetrieval) // Only delete text if copied successfully
980 // Keep a copy of the current input style.
981 InputStyle currentInputStyle;
982 currentInputStyle.Copy(mEventData->mInputStyle);
984 // Set as input style the style of the first deleted character.
985 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
987 // Compare if the input style has changed.
988 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
990 if(hasInputStyleChanged)
992 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
993 // Queue the input style changed signal.
994 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
997 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
999 // Mark the paragraphs to be updated.
1000 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1002 mTextUpdateInfo.mCharacterIndex = 0;
1003 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1004 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1005 mTextUpdateInfo.mClearAll = true;
1009 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1010 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1013 // Delete text between handles
1014 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1015 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1016 utf32Characters.Erase(first, last);
1018 // Will show the cursor at the first character of the selection.
1019 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1023 // Will show the cursor at the last character of the selection.
1024 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1027 mEventData->mDecoratorUpdated = true;
1031 void Controller::Impl::SetSelection(int start, int end)
1033 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1034 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1036 mEventData->mLeftSelectionPosition = start;
1037 mEventData->mRightSelectionPosition = end;
1038 mEventData->mUpdateCursorPosition = true;
1040 if(mSelectableControlInterface != nullptr)
1042 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1046 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1048 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1051 void Controller::Impl::ShowClipboard()
1055 mClipboard.ShowClipboard();
1059 void Controller::Impl::HideClipboard()
1061 if(mClipboard && mClipboardHideEnabled)
1063 mClipboard.HideClipboard();
1067 void Controller::Impl::SetClipboardHideEnable(bool enable)
1069 mClipboardHideEnabled = enable;
1072 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1074 //Send string to clipboard
1075 return (mClipboard && mClipboard.SetItem(source));
1078 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1080 std::string selectedText;
1081 RetrieveSelection(selectedText, deleteAfterSending);
1082 CopyStringToClipboard(selectedText);
1083 ChangeState(EventData::EDITING);
1086 void Controller::Impl::RequestGetTextFromClipboard()
1090 mClipboard.RequestItem();
1094 void Controller::Impl::RepositionSelectionHandles()
1096 SelectionHandleController::Reposition(*this);
1098 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1100 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1103 void Controller::Impl::SetPopupButtons()
1106 * Sets the Popup buttons to be shown depending on State.
1108 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1110 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1113 bool isEditable = IsEditable();
1114 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1116 if(EventData::SELECTING == mEventData->mState)
1118 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1121 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1124 if(!IsClipboardEmpty())
1128 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1130 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1133 if(!mEventData->mAllTextSelected)
1135 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1138 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1140 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1142 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1145 if(!IsClipboardEmpty())
1149 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1151 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1154 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1156 if(!IsClipboardEmpty())
1160 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1162 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1166 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1169 void Controller::Impl::ChangeState(EventData::State newState)
1171 ChangeTextControllerState(*this, newState);
1174 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1175 CursorInfo& cursorInfo)
1177 if(!IsShowingRealText())
1179 // Do not want to use the place-holder text to set the cursor position.
1181 // Use the line's height of the font's family set to set the cursor's size.
1182 // If there is no font's family set, use the default font.
1183 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1185 cursorInfo.lineOffset = 0.f;
1186 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1187 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1190 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1192 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1195 switch(mModel->mHorizontalAlignment)
1197 case Text::HorizontalAlignment::BEGIN:
1201 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1205 cursorInfo.primaryPosition.x = 0.f;
1209 case Text::HorizontalAlignment::CENTER:
1211 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1214 case Text::HorizontalAlignment::END:
1218 cursorInfo.primaryPosition.x = 0.f;
1222 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1228 // Nothing else to do.
1232 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1233 GetCursorPositionParameters parameters;
1234 parameters.visualModel = mModel->mVisualModel;
1235 parameters.logicalModel = mModel->mLogicalModel;
1236 parameters.metrics = mMetrics;
1237 parameters.logical = logical;
1238 parameters.isMultiline = isMultiLine;
1240 float defaultFontLineHeight = GetDefaultFontLineHeight();
1242 Text::GetCursorPosition(parameters,
1243 defaultFontLineHeight,
1246 // Adds Outline offset.
1247 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1248 cursorInfo.primaryPosition.x += outlineWidth;
1249 cursorInfo.primaryPosition.y += outlineWidth;
1250 cursorInfo.secondaryPosition.x += outlineWidth;
1251 cursorInfo.secondaryPosition.y += outlineWidth;
1255 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1257 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1258 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1260 if(0.f > cursorInfo.primaryPosition.x)
1262 cursorInfo.primaryPosition.x = 0.f;
1265 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1266 if(cursorInfo.primaryPosition.x > edgeWidth)
1268 cursorInfo.primaryPosition.x = edgeWidth;
1273 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1275 if(nullptr == mEventData)
1277 // Nothing to do if there is no text input.
1281 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1283 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1284 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1286 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1287 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1289 if(numberOfCharacters > 1u)
1291 const Script script = mModel->mLogicalModel->GetScript(index);
1292 if(HasLigatureMustBreak(script))
1294 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1295 numberOfCharacters = 1u;
1300 while(0u == numberOfCharacters)
1303 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1307 if(index < mEventData->mPrimaryCursorPosition)
1309 cursorIndex -= numberOfCharacters;
1313 cursorIndex += numberOfCharacters;
1316 // Will update the cursor hook position.
1317 mEventData->mUpdateCursorHookPosition = true;
1322 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1324 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1325 if(nullptr == mEventData)
1327 // Nothing to do if there is no text input.
1328 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1332 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1334 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1336 // Sets the cursor position.
1337 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1340 cursorInfo.primaryCursorHeight,
1341 cursorInfo.lineHeight);
1342 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1344 if(mEventData->mUpdateGrabHandlePosition)
1346 // Sets the grab handle position.
1347 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1349 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1350 cursorInfo.lineHeight);
1353 if(cursorInfo.isSecondaryCursor)
1355 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1356 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1357 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1358 cursorInfo.secondaryCursorHeight,
1359 cursorInfo.lineHeight);
1360 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1363 // Set which cursors are active according the state.
1364 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1366 if(cursorInfo.isSecondaryCursor)
1368 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1372 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1377 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1380 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1383 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1384 const CursorInfo& cursorInfo)
1386 SelectionHandleController::Update(*this, handleType, cursorInfo);
1389 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1391 // Clamp between -space & -alignment offset.
1393 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1395 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1396 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1397 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1399 mEventData->mDecoratorUpdated = true;
1403 mModel->mScrollPosition.x = 0.f;
1407 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1409 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1411 // Nothing to do if the text is single line.
1415 // Clamp between -space & 0.
1416 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1418 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1419 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1420 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1422 mEventData->mDecoratorUpdated = true;
1426 mModel->mScrollPosition.y = 0.f;
1430 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1432 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1434 // position is in actor's coords.
1435 const float positionEndX = position.x + cursorWidth;
1436 const float positionEndY = position.y + lineHeight;
1438 // Transform the position to decorator coords.
1439 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1440 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1442 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1443 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1445 if(decoratorPositionBeginX < 0.f)
1447 mModel->mScrollPosition.x = -position.x;
1449 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1451 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1454 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1456 if(decoratorPositionBeginY < 0.f)
1458 mModel->mScrollPosition.y = -position.y;
1460 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1462 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1464 else if(mModel->mLogicalModel->mText.Count() == 0u)
1466 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1471 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1473 // Get the current cursor position in decorator coords.
1474 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1476 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1478 // Calculate the offset to match the cursor position before the character was deleted.
1479 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1481 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1482 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1484 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1485 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1488 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1489 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1491 // Makes the new cursor position visible if needed.
1492 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1495 void Controller::Impl::ScrollTextToMatchCursor()
1497 CursorInfo cursorInfo;
1498 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1499 ScrollTextToMatchCursor(cursorInfo);
1502 void Controller::Impl::RequestRelayout()
1504 if(nullptr != mControlInterface)
1506 mControlInterface->RequestTextRelayout();
1510 void Controller::Impl::RelayoutAllCharacters()
1512 // relayout all characters
1513 mTextUpdateInfo.mCharacterIndex = 0;
1514 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1515 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1516 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1518 mTextUpdateInfo.mFullRelayoutNeeded = true;
1520 // Need to recalculate natural size
1521 mRecalculateNaturalSize = true;
1524 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1526 ChangeState(EventData::EDITING);
1532 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1534 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1537 void Controller::Impl::ProcessInputStyleChangedSignals()
1541 if(mEditableControlInterface)
1543 // Emit the input style changed signal for each mask
1544 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1545 mEventData->mInputStyleChangedQueue.end(),
1546 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1549 mEventData->mInputStyleChangedQueue.Clear();
1553 void Controller::Impl::ScrollBy(Vector2 scroll)
1555 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1557 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1558 const Vector2 currentScroll = mModel->mScrollPosition;
1560 scroll.x = -scroll.x;
1561 scroll.y = -scroll.y;
1563 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1565 mModel->mScrollPosition.x += scroll.x;
1566 ClampHorizontalScroll(layoutSize);
1569 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1571 mModel->mScrollPosition.y += scroll.y;
1572 ClampVerticalScroll(layoutSize);
1575 if(mModel->mScrollPosition != currentScroll)
1577 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1583 float Controller::Impl::GetHorizontalScrollPosition()
1585 // Scroll values are negative internally so we convert them to positive numbers
1586 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1589 float Controller::Impl::GetVerticalScrollPosition()
1591 // Scroll values are negative internally so we convert them to positive numbers
1592 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1595 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1598 return Vector3(10.f, 10.f, 10.f);
1601 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1604 return Vector2(10.f, 10.f);
1607 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1609 auto actor = Toolkit::TextAnchor::New();
1610 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1611 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1612 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1613 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1614 const Vector2 anchorSize = GetAnchorSize(anchor);
1615 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1616 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1617 actor.SetProperty(Actor::Property::NAME, anchorText);
1618 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1619 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1620 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1624 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1626 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1627 we need to create and destroy potentially many actors. Some optimization can be considered here.
1628 Maybe a "dirty" flag in mLogicalModel? */
1629 anchorActors.clear();
1630 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1632 auto actor = CreateAnchorActor(anchor);
1633 anchorActors.push_back(actor);
1637 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1639 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1641 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1646 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1649 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1651 //Underlined character runs for markup-processor
1652 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1653 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1654 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1656 if(shouldClearPreUnderlineRuns)
1658 mModel->mVisualModel->mUnderlineRuns.Clear();
1661 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1663 CharacterIndex characterIndex = it->characterRun.characterIndex;
1664 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1666 if(numberOfCharacters == 0)
1671 // Create one run for all glyphs of all run's characters that has same properties
1672 // This enhance performance and reduce the needed memory to store glyphs-runs
1673 UnderlinedGlyphRun underlineGlyphRun;
1674 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1675 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1676 //Copy properties (attributes)
1677 underlineGlyphRun.properties = it->properties;
1679 for(Length index = 1u; index < numberOfCharacters; index++)
1681 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1684 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1688 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1690 //Strikethrough character runs from markup-processor
1691 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1692 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1693 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1695 mModel->mVisualModel->mStrikethroughRuns.Clear();
1697 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1699 CharacterIndex characterIndex = it->characterRun.characterIndex;
1700 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1702 if(numberOfCharacters == 0)
1707 StrikethroughGlyphRun strikethroughGlyphRun;
1708 strikethroughGlyphRun.properties = it->properties;
1709 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1710 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1712 for(Length index = 1u; index < numberOfCharacters; index++)
1714 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1717 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1721 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1723 //CharacterSpacing character runs from markup-processor
1724 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1725 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1726 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1728 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1730 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1732 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1733 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1735 if(numberOfCharacters == 0)
1740 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1741 characterSpacingGlyphRun.value = it->value;
1742 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1743 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1745 for(Length index = 1u; index < numberOfCharacters; index++)
1747 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1750 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1754 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1756 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1758 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1761 UPDATE_LAYOUT_SIZE |
1766 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1767 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1771 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1774 mIsAutoScrollEnabled = enable;
1779 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1780 mIsAutoScrollEnabled = false;
1784 void Controller::Impl::SetEnableCursorBlink(bool enable)
1786 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1790 mEventData->mCursorBlinkEnabled = enable;
1792 if(!enable && mEventData->mDecorator)
1794 mEventData->mDecorator->StopCursorBlink();
1799 void Controller::Impl::SetMultiLineEnabled(bool enable)
1801 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1803 if(layout != mLayoutEngine.GetLayout())
1805 // Set the layout type.
1806 mLayoutEngine.SetLayout(layout);
1808 // Set the flags to redo the layout operations
1809 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1810 UPDATE_LAYOUT_SIZE |
1814 mTextUpdateInfo.mFullRelayoutNeeded = true;
1815 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1817 // Need to recalculate natural size
1818 mRecalculateNaturalSize = true;
1824 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1826 if(alignment != mModel->mHorizontalAlignment)
1828 // Set the alignment.
1829 mModel->mHorizontalAlignment = alignment;
1830 UpdateCursorPositionForAlignment(*this, true);
1835 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1837 if(alignment != mModel->mVerticalAlignment)
1839 // Set the alignment.
1840 mModel->mVerticalAlignment = alignment;
1841 UpdateCursorPositionForAlignment(*this, false);
1846 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1848 if(lineWrapMode != mModel->mLineWrapMode)
1850 // Update Text layout for applying wrap mode
1851 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1854 UPDATE_LAYOUT_SIZE |
1857 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1858 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1860 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1863 // Set the text wrap mode.
1864 mModel->mLineWrapMode = lineWrapMode;
1866 mTextUpdateInfo.mCharacterIndex = 0u;
1867 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1868 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1875 void Controller::Impl::SetDefaultColor(const Vector4& color)
1879 if(!IsShowingPlaceholderText())
1881 mModel->mVisualModel->SetTextColor(color);
1882 mModel->mLogicalModel->mColorRuns.Clear();
1883 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1888 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1890 mIsUserInteractionEnabled = enabled;
1892 if(mEventData && mEventData->mDecorator)
1894 bool editable = mEventData->mEditingEnabled && enabled;
1895 mEventData->mDecorator->SetEditable(editable);
1899 void Controller::Impl::ClearFontData()
1903 mFontDefaults->mFontId = 0u; // Remove old font ID
1906 // Set flags to update the model.
1907 mTextUpdateInfo.mCharacterIndex = 0u;
1908 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1909 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1911 mTextUpdateInfo.mClearAll = true;
1912 mTextUpdateInfo.mFullRelayoutNeeded = true;
1913 mRecalculateNaturalSize = true;
1915 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1921 UPDATE_LAYOUT_SIZE |
1926 void Controller::Impl::ClearStyleData()
1928 mModel->mLogicalModel->mColorRuns.Clear();
1929 mModel->mLogicalModel->ClearFontDescriptionRuns();
1930 mModel->mLogicalModel->ClearStrikethroughRuns();
1933 void Controller::Impl::ResetScrollPosition()
1937 // Reset the scroll position.
1938 mModel->mScrollPosition = Vector2::ZERO;
1939 mEventData->mScrollAfterUpdatePosition = true;
1943 } // namespace Dali::Toolkit::Text