2 * Copyright (c) 2023 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/controller/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/controller/text-controller-impl-data-clearer.h>
31 #include <dali-toolkit/internal/text/controller/text-controller-impl-event-handler.h>
32 #include <dali-toolkit/internal/text/controller/text-controller-impl-model-updater.h>
33 #include <dali-toolkit/internal/text/controller/text-controller-placeholder-handler.h>
34 #include <dali-toolkit/internal/text/controller/text-controller-relayouter.h>
35 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
36 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
37 #include <dali-toolkit/internal/text/text-control-interface.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 char* 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 Length Controller::Impl::GetNumberOfCharacters() const
528 if(!IsShowingPlaceholderText())
530 return mModel->GetNumberOfCharacters();
534 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetNumberOfCharacters %p empty (but showing placeholder)\n", this);
539 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
541 // Get the total number of characters.
542 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
544 // Retrieve the text.
545 if(0u != numberOfCharacters)
547 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
551 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
553 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
554 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
556 Window window = DevelWindow::Get(actor);
557 return static_cast<Dali::LayoutDirection::Type>(window ? window.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
561 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
565 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
567 if(mUpdateTextDirection)
569 // Operations that can be done only once until the text changes.
570 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
578 // Set the update info to relayout the whole text.
579 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
580 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
582 // Make sure the model is up-to-date before layouting
583 UpdateModel(onlyOnceOperations);
586 Relayouter::DoRelayout(*this,
587 Size(MAX_FLOAT, MAX_FLOAT),
588 static_cast<OperationsMask>(onlyOnceOperations |
589 LAYOUT | REORDER | UPDATE_DIRECTION),
590 naturalSize.GetVectorXY());
592 // Do not do again the only once operations.
593 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
595 // Clear the update info. This info will be set the next time the text is updated.
596 mTextUpdateInfo.Clear();
598 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
599 mTextUpdateInfo.mFullRelayoutNeeded = true;
601 mUpdateTextDirection = false;
604 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
607 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
609 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
610 mTextUpdateInfo.mStartGlyphIndex = 0u;
611 mTextUpdateInfo.mStartLineIndex = 0u;
612 numberOfCharacters = 0u;
614 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
615 if(0u == numberOfParagraphs)
617 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
618 numberOfCharacters = 0u;
620 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
622 // Nothing else to do if there are no paragraphs.
626 // Find the paragraphs to be updated.
627 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
628 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
630 // Text is being added at the end of the current text.
631 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
633 // Text is being added in a new paragraph after the last character of the text.
634 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
635 numberOfCharacters = 0u;
636 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
638 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
639 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
641 // Nothing else to do;
645 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
649 Length numberOfCharactersToUpdate = 0u;
650 if(mTextUpdateInfo.mFullRelayoutNeeded)
652 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
656 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
658 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
659 numberOfCharactersToUpdate,
660 paragraphsToBeUpdated);
663 if(0u != paragraphsToBeUpdated.Count())
665 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
666 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
667 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
669 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
670 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
672 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
673 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
674 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
675 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
677 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
678 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
680 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
684 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
688 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
689 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
692 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
694 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
697 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
699 return ControllerImplModelUpdater::Update(*this, operationsRequired);
702 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
704 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
707 float Controller::Impl::GetDefaultFontLineHeight()
709 FontId defaultFontId = 0u;
710 if(nullptr == mFontDefaults)
712 TextAbstraction::FontDescription fontDescription;
713 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
717 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
720 Text::FontMetrics fontMetrics;
721 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
723 return (fontMetrics.ascender - fontMetrics.descender);
726 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
728 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
730 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
732 RelayoutAllCharacters();
738 bool Controller::Impl::SetDefaultLineSize(float lineSize)
740 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
742 mLayoutEngine.SetDefaultLineSize(lineSize);
744 RelayoutAllCharacters();
750 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
752 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
754 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
756 RelayoutAllCharacters();
762 float Controller::Impl::GetRelativeLineSize()
764 return mLayoutEngine.GetRelativeLineSize();
767 string Controller::Impl::GetSelectedText()
770 if(EventData::SELECTING == mEventData->mState)
772 RetrieveSelection(text, false);
777 string Controller::Impl::CopyText()
780 RetrieveSelection(text, false);
781 SendSelectionToClipboard(false); // Text not modified
783 mEventData->mUpdateCursorPosition = true;
785 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
790 string Controller::Impl::CutText()
793 RetrieveSelection(text, false);
800 SendSelectionToClipboard(true); // Synchronous call to modify text
801 mOperationsPending = ALL_OPERATIONS;
803 if((0u != mModel->mLogicalModel->mText.Count()) ||
804 !IsPlaceholderAvailable())
806 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
810 PlaceholderHandler::ShowPlaceholderText(*this);
813 mEventData->mUpdateCursorPosition = true;
814 mEventData->mScrollAfterDelete = true;
818 if(nullptr != mEditableControlInterface)
820 mEditableControlInterface->TextChanged(true);
825 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
827 if(nullptr == mEventData)
829 // Nothing to do if there is no text.
833 if(mEventData->mSelectionEnabled && (pStart || pEnd))
835 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
836 uint32_t oldStart = mEventData->mLeftSelectionPosition;
837 uint32_t oldEnd = mEventData->mRightSelectionPosition;
841 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
845 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
848 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
850 ChangeState(EventData::EDITING);
851 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
852 mEventData->mUpdateCursorPosition = true;
856 ChangeState(EventData::SELECTING);
857 mEventData->mUpdateHighlightBox = true;
858 mEventData->mUpdateLeftSelectionPosition = true;
859 mEventData->mUpdateRightSelectionPosition = true;
862 if(mSelectableControlInterface != nullptr)
864 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
869 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
871 if(nullptr == mEventData)
875 return mEventData->mPrimaryCursorPosition;
878 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
880 if(nullptr == mEventData)
882 // Nothing to do if there is no text.
886 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
888 // Nothing for same cursor position.
892 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
893 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
894 mEventData->mPrimaryCursorPosition = std::min(index, length);
895 // If there is no focus, only the value is updated.
898 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
899 uint32_t oldStart = mEventData->mLeftSelectionPosition;
900 uint32_t oldEnd = mEventData->mRightSelectionPosition;
901 ChangeState(EventData::EDITING);
902 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
903 mEventData->mUpdateCursorPosition = true;
905 if(mSelectableControlInterface != nullptr && wasInSelectingState)
907 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
910 ScrollTextToMatchCursor();
913 if(nullptr != mEditableControlInterface)
915 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
921 Uint32Pair Controller::Impl::GetTextSelectionRange() const
927 range.first = mEventData->mLeftSelectionPosition;
928 range.second = mEventData->mRightSelectionPosition;
934 bool Controller::Impl::IsEditable() const
936 return mEventData && mEventData->mEditingEnabled;
939 void Controller::Impl::SetEditable(bool editable)
943 mEventData->mEditingEnabled = editable;
945 if(mEventData->mDecorator)
947 bool decoratorEditable = editable && mIsUserInteractionEnabled;
948 mEventData->mDecorator->SetEditable(decoratorEditable);
953 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
955 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
957 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
959 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
960 mFontDefaults->mFontDescription.family = newDefaultFont;
968 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
970 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
972 // Nothing to select if handles are in the same place.
973 selectedText.clear();
977 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
979 //Get start and end position of selection
980 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
981 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
983 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
984 const Length numberOfCharacters = utf32Characters.Count();
986 // Validate the start and end selection points
987 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
989 //Get text as a UTF8 string
990 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
992 if(deleteAfterRetrieval) // Only delete text if copied successfully
994 // Keep a copy of the current input style.
995 InputStyle currentInputStyle;
996 currentInputStyle.Copy(mEventData->mInputStyle);
998 // Set as input style the style of the first deleted character.
999 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
1001 // Compare if the input style has changed.
1002 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
1004 if(hasInputStyleChanged)
1006 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
1007 // Queue the input style changed signal.
1008 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
1011 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1013 // Mark the paragraphs to be updated.
1014 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1016 mTextUpdateInfo.mCharacterIndex = 0;
1017 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1018 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1019 mTextUpdateInfo.mClearAll = true;
1023 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1024 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1027 // Delete text between handles
1028 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1029 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1030 utf32Characters.Erase(first, last);
1032 // Will show the cursor at the first character of the selection.
1033 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1037 // Will show the cursor at the last character of the selection.
1038 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1041 mEventData->mDecoratorUpdated = true;
1045 void Controller::Impl::SetSelection(int start, int end)
1047 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1048 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1050 mEventData->mLeftSelectionPosition = start;
1051 mEventData->mRightSelectionPosition = end;
1052 mEventData->mUpdateCursorPosition = true;
1054 if(mSelectableControlInterface != nullptr)
1056 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1060 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1062 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1065 void Controller::Impl::ShowClipboard()
1067 if(EnsureClipboardCreated())
1069 mClipboard.ShowClipboard();
1073 void Controller::Impl::HideClipboard()
1075 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1077 mClipboard.HideClipboard();
1081 void Controller::Impl::SetClipboardHideEnable(bool enable)
1083 mClipboardHideEnabled = enable;
1086 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1088 //Send string to clipboard
1089 return (EnsureClipboardCreated() && mClipboard.SetItem(source));
1092 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1094 std::string selectedText;
1095 RetrieveSelection(selectedText, deleteAfterSending);
1096 CopyStringToClipboard(selectedText);
1097 ChangeState(EventData::EDITING);
1100 void Controller::Impl::RequestGetTextFromClipboard()
1102 if(EnsureClipboardCreated())
1104 mClipboard.RequestItem();
1108 void Controller::Impl::RepositionSelectionHandles()
1110 SelectionHandleController::Reposition(*this);
1112 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1114 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1117 void Controller::Impl::SetPopupButtons()
1120 * Sets the Popup buttons to be shown depending on State.
1122 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1124 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1127 bool isEditable = IsEditable();
1128 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1130 if(EventData::SELECTING == mEventData->mState)
1132 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1135 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1138 if(!IsClipboardEmpty())
1142 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1144 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1147 if(!mEventData->mAllTextSelected)
1149 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1152 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1154 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1156 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1159 if(!IsClipboardEmpty())
1163 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1165 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1168 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1170 if(!IsClipboardEmpty())
1174 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1176 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1180 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1183 void Controller::Impl::ChangeState(EventData::State newState)
1185 ChangeTextControllerState(*this, newState);
1188 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1189 CursorInfo& cursorInfo)
1191 if(!IsShowingRealText())
1193 // Do not want to use the place-holder text to set the cursor position.
1195 // Use the line's height of the font's family set to set the cursor's size.
1196 // If there is no font's family set, use the default font.
1197 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1199 cursorInfo.lineOffset = 0.f;
1200 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1201 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1204 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1206 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1209 switch(mModel->mHorizontalAlignment)
1211 case Text::HorizontalAlignment::BEGIN:
1215 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1219 cursorInfo.primaryPosition.x = 0.f;
1223 case Text::HorizontalAlignment::CENTER:
1225 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1228 case Text::HorizontalAlignment::END:
1232 cursorInfo.primaryPosition.x = 0.f;
1236 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1242 // Nothing else to do.
1246 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1247 GetCursorPositionParameters parameters;
1248 parameters.visualModel = mModel->mVisualModel;
1249 parameters.logicalModel = mModel->mLogicalModel;
1250 parameters.metrics = mMetrics;
1251 parameters.logical = logical;
1252 parameters.isMultiline = isMultiLine;
1254 float defaultFontLineHeight = GetDefaultFontLineHeight();
1256 Text::GetCursorPosition(parameters,
1257 defaultFontLineHeight,
1260 // Adds Outline offset.
1261 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1262 cursorInfo.primaryPosition.x += outlineWidth;
1263 cursorInfo.primaryPosition.y += outlineWidth;
1264 cursorInfo.secondaryPosition.x += outlineWidth;
1265 cursorInfo.secondaryPosition.y += outlineWidth;
1269 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1271 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1272 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1274 if(0.f > cursorInfo.primaryPosition.x)
1276 cursorInfo.primaryPosition.x = 0.f;
1279 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1280 if(cursorInfo.primaryPosition.x > edgeWidth)
1282 cursorInfo.primaryPosition.x = edgeWidth;
1287 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1289 if(nullptr == mEventData)
1291 // Nothing to do if there is no text input.
1295 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1297 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1298 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1300 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1301 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1303 if(numberOfCharacters > 1u)
1305 const Script script = mModel->mLogicalModel->GetScript(index);
1306 if(HasLigatureMustBreak(script))
1308 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1309 numberOfCharacters = 1u;
1314 while(0u == numberOfCharacters)
1317 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1321 if(index < mEventData->mPrimaryCursorPosition)
1323 cursorIndex -= numberOfCharacters;
1327 cursorIndex += numberOfCharacters;
1330 // Will update the cursor hook position.
1331 mEventData->mUpdateCursorHookPosition = true;
1336 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1338 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1339 if(nullptr == mEventData)
1341 // Nothing to do if there is no text input.
1342 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1346 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1348 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1350 // Sets the cursor position.
1351 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1354 cursorInfo.primaryCursorHeight,
1355 cursorInfo.lineHeight);
1356 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1358 if(mEventData->mUpdateGrabHandlePosition)
1360 // Sets the grab handle position.
1361 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1363 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1364 cursorInfo.lineHeight);
1367 if(cursorInfo.isSecondaryCursor)
1369 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1370 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1371 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1372 cursorInfo.secondaryCursorHeight,
1373 cursorInfo.lineHeight);
1374 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1377 // Set which cursors are active according the state.
1378 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1380 if(cursorInfo.isSecondaryCursor)
1382 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1386 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1391 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1394 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1397 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1398 const CursorInfo& cursorInfo)
1400 SelectionHandleController::Update(*this, handleType, cursorInfo);
1403 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1405 // Clamp between -space & -alignment offset.
1407 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1409 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1410 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1411 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1413 mEventData->mDecoratorUpdated = true;
1417 mModel->mScrollPosition.x = 0.f;
1421 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1423 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1425 // Nothing to do if the text is single line.
1429 // Clamp between -space & 0.
1430 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1432 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1433 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1434 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1436 mEventData->mDecoratorUpdated = true;
1440 mModel->mScrollPosition.y = 0.f;
1444 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1446 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1448 // position is in actor's coords.
1449 const float positionEndX = position.x + cursorWidth;
1450 const float positionEndY = position.y + lineHeight;
1452 // Transform the position to decorator coords.
1453 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1454 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1456 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1457 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1459 if(decoratorPositionBeginX < 0.f)
1461 mModel->mScrollPosition.x = -position.x;
1463 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1465 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1468 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1470 if(decoratorPositionBeginY < 0.f)
1472 mModel->mScrollPosition.y = -position.y;
1474 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1476 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1478 else if(mModel->mLogicalModel->mText.Count() == 0u)
1480 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1485 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1487 // Get the current cursor position in decorator coords.
1488 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1490 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1492 // Calculate the offset to match the cursor position before the character was deleted.
1493 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1495 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1496 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1498 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1499 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1502 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1503 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1505 // Makes the new cursor position visible if needed.
1506 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1509 void Controller::Impl::ScrollTextToMatchCursor()
1511 CursorInfo cursorInfo;
1512 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1513 ScrollTextToMatchCursor(cursorInfo);
1516 void Controller::Impl::RequestRelayout()
1518 if(nullptr != mControlInterface)
1520 mControlInterface->RequestTextRelayout();
1524 void Controller::Impl::RelayoutAllCharacters()
1526 // relayout all characters
1527 mTextUpdateInfo.mCharacterIndex = 0;
1528 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1529 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1530 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1532 mTextUpdateInfo.mFullRelayoutNeeded = true;
1534 // Need to recalculate natural size
1535 mRecalculateNaturalSize = true;
1538 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1540 ChangeState(EventData::EDITING);
1546 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1548 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1551 void Controller::Impl::ProcessInputStyleChangedSignals()
1555 if(mEditableControlInterface)
1557 // Emit the input style changed signal for each mask
1558 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1559 mEventData->mInputStyleChangedQueue.end(),
1560 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1563 mEventData->mInputStyleChangedQueue.Clear();
1567 void Controller::Impl::ScrollBy(Vector2 scroll)
1569 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1571 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1572 const Vector2 currentScroll = mModel->mScrollPosition;
1574 scroll.x = -scroll.x;
1575 scroll.y = -scroll.y;
1577 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1579 mModel->mScrollPosition.x += scroll.x;
1580 ClampHorizontalScroll(layoutSize);
1583 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1585 mModel->mScrollPosition.y += scroll.y;
1586 ClampVerticalScroll(layoutSize);
1589 if(mModel->mScrollPosition != currentScroll)
1591 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1597 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1599 bool isScrollable = false;
1602 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1603 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1604 if(isHorizontalScrollEnabled || isVerticalScrollEnabled)
1606 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1607 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1608 const Vector2& scrollPosition = mModel->mScrollPosition;
1610 if(isHorizontalScrollEnabled)
1612 const float displacementX = displacement.x;
1613 const float positionX = scrollPosition.x + displacementX;
1614 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1616 isScrollable = true;
1620 if(isVerticalScrollEnabled)
1622 const float displacementY = displacement.y;
1623 const float positionY = scrollPosition.y + displacementY;
1624 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1626 isScrollable = true;
1631 return isScrollable;
1634 float Controller::Impl::GetHorizontalScrollPosition()
1636 // Scroll values are negative internally so we convert them to positive numbers
1637 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1640 float Controller::Impl::GetVerticalScrollPosition()
1642 // Scroll values are negative internally so we convert them to positive numbers
1643 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1646 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1649 return Vector3(10.f, 10.f, 10.f);
1652 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1655 return Vector2(10.f, 10.f);
1658 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1660 auto actor = Toolkit::TextAnchor::New();
1661 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1662 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1663 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1664 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1665 const Vector2 anchorSize = GetAnchorSize(anchor);
1666 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1667 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1668 actor.SetProperty(Actor::Property::NAME, anchorText);
1669 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1670 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1671 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1675 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1677 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1678 we need to create and destroy potentially many actors. Some optimization can be considered here.
1679 Maybe a "dirty" flag in mLogicalModel? */
1680 anchorActors.clear();
1681 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1683 auto actor = CreateAnchorActor(anchor);
1684 anchorActors.push_back(actor);
1688 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1690 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1692 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1697 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1700 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1702 //Underlined character runs for markup-processor
1703 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1704 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1705 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1707 if(shouldClearPreUnderlineRuns)
1709 mModel->mVisualModel->mUnderlineRuns.Clear();
1712 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1714 CharacterIndex characterIndex = it->characterRun.characterIndex;
1715 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1717 if(numberOfCharacters == 0)
1722 // Create one run for all glyphs of all run's characters that has same properties
1723 // This enhance performance and reduce the needed memory to store glyphs-runs
1724 UnderlinedGlyphRun underlineGlyphRun;
1725 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1726 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1727 //Copy properties (attributes)
1728 underlineGlyphRun.properties = it->properties;
1730 for(Length index = 1u; index < numberOfCharacters; index++)
1732 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1735 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1738 // Reset flag. The updates have been applied from logical to visual.
1739 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1742 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1744 //Strikethrough character runs from markup-processor
1745 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1746 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1747 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1749 mModel->mVisualModel->mStrikethroughRuns.Clear();
1751 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1753 CharacterIndex characterIndex = it->characterRun.characterIndex;
1754 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1756 if(numberOfCharacters == 0)
1761 StrikethroughGlyphRun strikethroughGlyphRun;
1762 strikethroughGlyphRun.properties = it->properties;
1763 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1764 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1766 for(Length index = 1u; index < numberOfCharacters; index++)
1768 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1771 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1774 // Reset flag. The updates have been applied from logical to visual.
1775 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1778 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1780 //CharacterSpacing character runs from markup-processor
1781 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1782 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1783 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1785 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1787 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1789 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1790 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1792 if(numberOfCharacters == 0)
1797 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1798 characterSpacingGlyphRun.value = it->value;
1799 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1800 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1802 for(Length index = 1u; index < numberOfCharacters; index++)
1804 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1807 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1809 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1812 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1814 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1816 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1819 UPDATE_LAYOUT_SIZE |
1824 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1825 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1829 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1832 mIsAutoScrollEnabled = enable;
1837 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1838 mIsAutoScrollEnabled = false;
1842 void Controller::Impl::SetEnableCursorBlink(bool enable)
1844 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1848 mEventData->mCursorBlinkEnabled = enable;
1850 if(!enable && mEventData->mDecorator)
1852 mEventData->mDecorator->StopCursorBlink();
1857 void Controller::Impl::SetMultiLineEnabled(bool enable)
1859 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1861 if(layout != mLayoutEngine.GetLayout())
1863 // Set the layout type.
1864 mLayoutEngine.SetLayout(layout);
1866 // Set the flags to redo the layout operations
1867 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1868 UPDATE_LAYOUT_SIZE |
1872 mTextUpdateInfo.mFullRelayoutNeeded = true;
1873 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1875 // Need to recalculate natural size
1876 mRecalculateNaturalSize = true;
1882 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1884 if(alignment != mModel->mHorizontalAlignment)
1886 // Set the alignment.
1887 mModel->mHorizontalAlignment = alignment;
1888 UpdateCursorPositionForAlignment(*this, true);
1893 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1895 if(alignment != mModel->mVerticalAlignment)
1897 // Set the alignment.
1898 mModel->mVerticalAlignment = alignment;
1899 UpdateCursorPositionForAlignment(*this, false);
1904 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1906 if(lineWrapMode != mModel->mLineWrapMode)
1908 // Update Text layout for applying wrap mode
1909 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1912 UPDATE_LAYOUT_SIZE |
1915 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1916 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1918 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1921 // Set the text wrap mode.
1922 mModel->mLineWrapMode = lineWrapMode;
1924 mTextUpdateInfo.mCharacterIndex = 0u;
1925 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1926 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1933 void Controller::Impl::SetDefaultColor(const Vector4& color)
1937 if(!IsShowingPlaceholderText())
1939 mModel->mVisualModel->SetTextColor(color);
1940 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1945 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1947 mIsUserInteractionEnabled = enabled;
1949 if(mEventData && mEventData->mDecorator)
1951 bool editable = mEventData->mEditingEnabled && enabled;
1952 mEventData->mDecorator->SetEditable(editable);
1956 void Controller::Impl::ClearFontData()
1960 mFontDefaults->mFontId = 0u; // Remove old font ID
1963 // Set flags to update the model.
1964 mTextUpdateInfo.mCharacterIndex = 0u;
1965 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1966 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1968 mTextUpdateInfo.mClearAll = true;
1969 mTextUpdateInfo.mFullRelayoutNeeded = true;
1970 mRecalculateNaturalSize = true;
1972 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1978 UPDATE_LAYOUT_SIZE |
1983 void Controller::Impl::ClearStyleData()
1985 mModel->mLogicalModel->mColorRuns.Clear();
1986 mModel->mLogicalModel->ClearFontDescriptionRuns();
1987 mModel->mLogicalModel->ClearStrikethroughRuns();
1990 void Controller::Impl::ResetScrollPosition()
1994 // Reset the scroll position.
1995 mModel->mScrollPosition = Vector2::ZERO;
1996 mEventData->mScrollAfterUpdatePosition = true;
2000 } // namespace Dali::Toolkit::Text