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/integration-api/adaptor-framework/scene-holder.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 = "";
55 const char* MIME_TYPE_TEXT_PLAIN = "text/plain;charset=utf-8";
59 namespace Dali::Toolkit::Text
63 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
65 // Sets the default text's color.
66 inputStyle.textColor = textColor;
67 inputStyle.isDefaultColor = true;
69 inputStyle.familyName.clear();
70 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
71 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
72 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
73 inputStyle.size = 0.f;
75 inputStyle.lineSpacing = 0.f;
77 inputStyle.underlineProperties.clear();
78 inputStyle.shadowProperties.clear();
79 inputStyle.embossProperties.clear();
80 inputStyle.outlineProperties.clear();
82 inputStyle.isFamilyDefined = false;
83 inputStyle.isWeightDefined = false;
84 inputStyle.isWidthDefined = false;
85 inputStyle.isSlantDefined = false;
86 inputStyle.isSizeDefined = false;
88 inputStyle.isLineSpacingDefined = false;
90 inputStyle.isUnderlineDefined = false;
91 inputStyle.isShadowDefined = false;
92 inputStyle.isEmbossDefined = false;
93 inputStyle.isOutlineDefined = false;
95 // Sets the default font's family name, weight, width, slant and size.
98 if(fontDefaults->familyDefined)
100 inputStyle.familyName = fontDefaults->mFontDescription.family;
101 inputStyle.isFamilyDefined = true;
104 if(fontDefaults->weightDefined)
106 inputStyle.weight = fontDefaults->mFontDescription.weight;
107 inputStyle.isWeightDefined = true;
110 if(fontDefaults->widthDefined)
112 inputStyle.width = fontDefaults->mFontDescription.width;
113 inputStyle.isWidthDefined = true;
116 if(fontDefaults->slantDefined)
118 inputStyle.slant = fontDefaults->mFontDescription.slant;
119 inputStyle.isSlantDefined = true;
122 if(fontDefaults->sizeDefined)
124 inputStyle.size = fontDefaults->mDefaultPointSize;
125 inputStyle.isSizeDefined = true;
130 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
132 EventData* eventData = impl.mEventData;
134 if(nullptr == eventData)
136 // Nothing to do if there is no text input.
140 DecoratorPtr& decorator = eventData->mDecorator;
143 // Nothing to do if there is no decorator.
147 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
149 if(eventData->mState != newState)
151 eventData->mPreviousState = eventData->mState;
152 eventData->mState = newState;
154 switch(eventData->mState)
156 case EventData::INACTIVE:
158 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
159 decorator->StopCursorBlink();
160 decorator->SetHandleActive(GRAB_HANDLE, false);
161 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
162 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
163 decorator->SetHighlightActive(false);
164 decorator->SetPopupActive(false);
165 eventData->mDecoratorUpdated = true;
169 case EventData::INTERRUPTED:
171 decorator->SetHandleActive(GRAB_HANDLE, false);
172 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
173 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
174 decorator->SetHighlightActive(false);
175 decorator->SetPopupActive(false);
176 eventData->mDecoratorUpdated = true;
180 case EventData::SELECTING:
182 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
183 decorator->StopCursorBlink();
184 decorator->SetHandleActive(GRAB_HANDLE, false);
185 if(eventData->mGrabHandleEnabled)
187 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
188 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
190 decorator->SetHighlightActive(true);
191 if(eventData->mGrabHandlePopupEnabled)
193 impl.SetPopupButtons();
194 decorator->SetPopupActive(true);
196 eventData->mDecoratorUpdated = true;
200 case EventData::EDITING:
202 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
203 if(eventData->mCursorBlinkEnabled)
205 decorator->StartCursorBlink();
207 // Grab handle is not shown until a tap is received whilst EDITING
208 decorator->SetHandleActive(GRAB_HANDLE, false);
209 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
210 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
211 decorator->SetHighlightActive(false);
212 if(eventData->mGrabHandlePopupEnabled)
214 decorator->SetPopupActive(false);
216 eventData->mDecoratorUpdated = true;
219 case EventData::EDITING_WITH_POPUP:
221 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
223 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
224 if(eventData->mCursorBlinkEnabled)
226 decorator->StartCursorBlink();
228 if(eventData->mSelectionEnabled)
230 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
231 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
232 decorator->SetHighlightActive(false);
234 else if(eventData->mGrabHandleEnabled)
236 decorator->SetHandleActive(GRAB_HANDLE, true);
238 if(eventData->mGrabHandlePopupEnabled)
240 impl.SetPopupButtons();
241 decorator->SetPopupActive(true);
243 eventData->mDecoratorUpdated = true;
246 case EventData::EDITING_WITH_GRAB_HANDLE:
248 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
250 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
251 if(eventData->mCursorBlinkEnabled)
253 decorator->StartCursorBlink();
255 // Grab handle is not shown until a tap is received whilst EDITING
256 if(eventData->mGrabHandleEnabled)
258 decorator->SetHandleActive(GRAB_HANDLE, true);
260 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
261 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
262 decorator->SetHighlightActive(false);
263 if(eventData->mGrabHandlePopupEnabled)
265 decorator->SetPopupActive(false);
267 eventData->mDecoratorUpdated = true;
271 case EventData::SELECTION_HANDLE_PANNING:
273 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
274 decorator->StopCursorBlink();
275 decorator->SetHandleActive(GRAB_HANDLE, false);
276 if(eventData->mGrabHandleEnabled)
278 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
279 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
281 decorator->SetHighlightActive(true);
282 if(eventData->mGrabHandlePopupEnabled)
284 decorator->SetPopupActive(false);
286 eventData->mDecoratorUpdated = true;
290 case EventData::GRAB_HANDLE_PANNING:
292 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
294 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
295 if(eventData->mCursorBlinkEnabled)
297 decorator->StartCursorBlink();
299 if(eventData->mGrabHandleEnabled)
301 decorator->SetHandleActive(GRAB_HANDLE, true);
303 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
304 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
305 decorator->SetHighlightActive(false);
306 if(eventData->mGrabHandlePopupEnabled)
308 decorator->SetPopupActive(false);
310 eventData->mDecoratorUpdated = true;
314 case EventData::EDITING_WITH_PASTE_POPUP:
316 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
318 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
319 if(eventData->mCursorBlinkEnabled)
321 decorator->StartCursorBlink();
324 if(eventData->mGrabHandleEnabled)
326 decorator->SetHandleActive(GRAB_HANDLE, true);
328 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
329 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
330 decorator->SetHighlightActive(false);
332 if(eventData->mGrabHandlePopupEnabled)
334 impl.SetPopupButtons();
335 decorator->SetPopupActive(true);
337 eventData->mDecoratorUpdated = true;
341 case EventData::TEXT_PANNING:
343 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
344 decorator->StopCursorBlink();
345 decorator->SetHandleActive(GRAB_HANDLE, false);
346 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
347 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
349 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
350 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
351 decorator->SetHighlightActive(true);
354 if(eventData->mGrabHandlePopupEnabled)
356 decorator->SetPopupActive(false);
359 eventData->mDecoratorUpdated = true;
366 void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment)
368 EventData* eventData = impl.mEventData;
370 // Set the flag to redo the alignment operation
371 impl.mOperationsPending = static_cast<Controller::OperationsMask>(impl.mOperationsPending | Controller::OperationsMask::ALIGN);
375 // Note: mUpdateAlignment is currently only needed for horizontal alignment
376 eventData->mUpdateAlignment = needFullAlignment;
378 // Update the cursor if it's in editing mode
379 if(EventData::IsEditingState(eventData->mState))
381 impl.ChangeState(EventData::EDITING);
382 eventData->mUpdateCursorPosition = true;
387 } // unnamed Namespace
389 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
390 : mDecorator(decorator),
391 mInputMethodContext(inputMethodContext),
392 mPlaceholderFont(nullptr),
393 mPlaceholderTextActive(),
394 mPlaceholderTextInactive(),
395 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
397 mInputStyleChangedQueue(),
398 mPreviousState(INACTIVE),
400 mPrimaryCursorPosition(0u),
401 mLeftSelectionPosition(0u),
402 mRightSelectionPosition(0u),
403 mPreEditStartPosition(0u),
405 mCursorHookPositionX(0.f),
406 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
407 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
408 mIsShowingPlaceholderText(false),
410 mDecoratorUpdated(false),
411 mCursorBlinkEnabled(true),
412 mGrabHandleEnabled(true),
413 mGrabHandlePopupEnabled(true),
414 mSelectionEnabled(true),
415 mUpdateCursorHookPosition(false),
416 mUpdateCursorPosition(false),
417 mUpdateGrabHandlePosition(false),
418 mUpdateLeftSelectionPosition(false),
419 mUpdateRightSelectionPosition(false),
420 mIsLeftHandleSelected(false),
421 mIsRightHandleSelected(false),
422 mUpdateHighlightBox(false),
423 mScrollAfterUpdatePosition(false),
424 mScrollAfterDelete(false),
425 mAllTextSelected(false),
426 mUpdateInputStyle(false),
427 mPasswordInput(false),
428 mCheckScrollAmount(false),
429 mIsPlaceholderPixelSize(false),
430 mIsPlaceholderElideEnabled(false),
431 mPlaceholderEllipsisFlag(false),
432 mShiftSelectionFlag(true),
433 mUpdateAlignment(false),
434 mEditingEnabled(true)
438 bool Controller::Impl::ProcessInputEvents()
440 return ControllerImplEventHandler::ProcessInputEvents(*this);
443 void Controller::Impl::NotifyInputMethodContext()
445 if(mEventData && mEventData->mInputMethodContext)
447 CharacterIndex cursorPosition = GetLogicalCursorPosition();
448 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
449 mEventData->mInputMethodContext.NotifyCursorPosition();
453 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
455 if(mEventData && mEventData->mInputMethodContext)
457 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
458 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
462 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
464 CharacterIndex cursorPosition = 0u;
468 if((EventData::SELECTING == mEventData->mState) ||
469 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
471 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
475 cursorPosition = mEventData->mPrimaryCursorPosition;
479 return cursorPosition;
482 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
484 Length numberOfWhiteSpaces = 0u;
486 // Get the buffer to the text.
487 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
489 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
490 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
492 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
498 return numberOfWhiteSpaces;
501 void Controller::Impl::GetText(std::string& text) const
503 if(!IsShowingPlaceholderText())
505 // Retrieves the text string.
510 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
514 Length Controller::Impl::GetNumberOfCharacters() const
516 if(!IsShowingPlaceholderText())
518 return mModel->GetNumberOfCharacters();
522 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetNumberOfCharacters %p empty (but showing placeholder)\n", this);
527 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
529 // Get the total number of characters.
530 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
532 // Retrieve the text.
533 if(0u != numberOfCharacters)
535 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
539 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
541 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
542 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
544 Integration::SceneHolder sceneHolder = Integration::SceneHolder::Get(actor);
545 return static_cast<Dali::LayoutDirection::Type>(sceneHolder ? sceneHolder.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
549 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
553 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
555 if(mUpdateTextDirection)
557 // Operations that can be done only once until the text changes.
558 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
566 // Set the update info to relayout the whole text.
567 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
568 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
570 // Make sure the model is up-to-date before layouting
571 UpdateModel(onlyOnceOperations);
574 Relayouter::DoRelayout(*this,
575 Size(MAX_FLOAT, MAX_FLOAT),
576 static_cast<OperationsMask>(onlyOnceOperations |
577 LAYOUT | REORDER | UPDATE_DIRECTION),
578 naturalSize.GetVectorXY());
580 // Do not do again the only once operations.
581 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
583 // Clear the update info. This info will be set the next time the text is updated.
584 mTextUpdateInfo.Clear();
586 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
587 mTextUpdateInfo.mFullRelayoutNeeded = true;
589 mUpdateTextDirection = false;
592 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
595 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
597 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
598 mTextUpdateInfo.mStartGlyphIndex = 0u;
599 mTextUpdateInfo.mStartLineIndex = 0u;
600 numberOfCharacters = 0u;
602 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
603 if(0u == numberOfParagraphs)
605 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
606 numberOfCharacters = 0u;
608 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
610 // Nothing else to do if there are no paragraphs.
614 // Find the paragraphs to be updated.
615 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
616 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
618 // Text is being added at the end of the current text.
619 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
621 // Text is being added in a new paragraph after the last character of the text.
622 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
623 numberOfCharacters = 0u;
624 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
626 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
627 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
629 // Nothing else to do;
633 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
637 Length numberOfCharactersToUpdate = 0u;
638 if(mTextUpdateInfo.mFullRelayoutNeeded)
640 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
644 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
646 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
647 numberOfCharactersToUpdate,
648 paragraphsToBeUpdated);
651 if(0u != paragraphsToBeUpdated.Count())
653 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
654 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
655 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
657 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
658 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
660 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
661 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
662 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
663 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
665 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
666 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
668 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
672 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
676 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
677 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
680 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
682 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
685 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
687 return ControllerImplModelUpdater::Update(*this, operationsRequired);
690 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
692 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
695 float Controller::Impl::GetDefaultFontLineHeight()
697 FontId defaultFontId = 0u;
698 if(nullptr == mFontDefaults)
700 TextAbstraction::FontDescription fontDescription;
701 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
705 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
708 Text::FontMetrics fontMetrics;
709 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
711 return (fontMetrics.ascender - fontMetrics.descender);
714 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
716 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
718 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
720 RelayoutAllCharacters();
726 bool Controller::Impl::SetDefaultLineSize(float lineSize)
728 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
730 mLayoutEngine.SetDefaultLineSize(lineSize);
732 RelayoutAllCharacters();
738 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
740 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
742 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
744 RelayoutAllCharacters();
750 float Controller::Impl::GetRelativeLineSize()
752 return mLayoutEngine.GetRelativeLineSize();
755 string Controller::Impl::GetSelectedText()
758 if(EventData::SELECTING == mEventData->mState)
760 RetrieveSelection(text, false);
765 string Controller::Impl::CopyText()
768 RetrieveSelection(text, false);
769 SendSelectionToClipboard(false); // Text not modified
771 mEventData->mUpdateCursorPosition = true;
773 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
778 string Controller::Impl::CutText()
781 RetrieveSelection(text, false);
788 SendSelectionToClipboard(true); // Synchronous call to modify text
789 mOperationsPending = ALL_OPERATIONS;
791 if((0u != mModel->mLogicalModel->mText.Count()) ||
792 !IsPlaceholderAvailable())
794 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
798 PlaceholderHandler::ShowPlaceholderText(*this);
801 mEventData->mUpdateCursorPosition = true;
802 mEventData->mScrollAfterDelete = true;
806 if(nullptr != mEditableControlInterface)
808 mEditableControlInterface->TextChanged(true);
813 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
815 if(nullptr == mEventData)
817 // Nothing to do if there is no text.
821 if(mEventData->mSelectionEnabled && (pStart || pEnd))
823 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
824 uint32_t oldStart = mEventData->mLeftSelectionPosition;
825 uint32_t oldEnd = mEventData->mRightSelectionPosition;
829 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
833 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
836 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
838 ChangeState(EventData::EDITING);
839 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
840 mEventData->mUpdateCursorPosition = true;
844 ChangeState(EventData::SELECTING);
845 mEventData->mUpdateHighlightBox = true;
846 mEventData->mUpdateLeftSelectionPosition = true;
847 mEventData->mUpdateRightSelectionPosition = true;
850 if(mSelectableControlInterface != nullptr)
852 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
857 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
859 if(nullptr == mEventData)
863 return mEventData->mPrimaryCursorPosition;
866 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
868 if(nullptr == mEventData)
870 // Nothing to do if there is no text.
874 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
876 // Nothing for same cursor position.
880 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
881 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
882 mEventData->mPrimaryCursorPosition = std::min(index, length);
883 // If there is no focus, only the value is updated.
886 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
887 uint32_t oldStart = mEventData->mLeftSelectionPosition;
888 uint32_t oldEnd = mEventData->mRightSelectionPosition;
889 ChangeState(EventData::EDITING);
890 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
891 mEventData->mUpdateCursorPosition = true;
893 if(mSelectableControlInterface != nullptr && wasInSelectingState)
895 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
898 ScrollTextToMatchCursor();
901 if(nullptr != mEditableControlInterface)
903 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
909 Uint32Pair Controller::Impl::GetTextSelectionRange() const
915 range.first = mEventData->mLeftSelectionPosition;
916 range.second = mEventData->mRightSelectionPosition;
922 bool Controller::Impl::IsEditable() const
924 return mEventData && mEventData->mEditingEnabled;
927 void Controller::Impl::SetEditable(bool editable)
931 mEventData->mEditingEnabled = editable;
933 if(mEventData->mDecorator)
935 bool decoratorEditable = editable && mIsUserInteractionEnabled;
936 mEventData->mDecorator->SetEditable(decoratorEditable);
941 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
943 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
945 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
947 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
948 mFontDefaults->mFontDescription.family = newDefaultFont;
956 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
958 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
960 // Nothing to select if handles are in the same place.
961 selectedText.clear();
965 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
967 //Get start and end position of selection
968 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
969 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
971 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
972 const Length numberOfCharacters = utf32Characters.Count();
974 // Validate the start and end selection points
975 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
977 //Get text as a UTF8 string
978 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
980 if(deleteAfterRetrieval) // Only delete text if copied successfully
982 // Keep a copy of the current input style.
983 InputStyle currentInputStyle;
984 currentInputStyle.Copy(mEventData->mInputStyle);
986 // Set as input style the style of the first deleted character.
987 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
989 // Compare if the input style has changed.
990 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
992 if(hasInputStyleChanged)
994 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
995 // Queue the input style changed signal.
996 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
999 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1001 // Mark the paragraphs to be updated.
1002 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1004 mTextUpdateInfo.mCharacterIndex = 0;
1005 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1006 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1007 mTextUpdateInfo.mClearAll = true;
1011 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1012 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1015 // Delete text between handles
1016 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1017 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1018 utf32Characters.Erase(first, last);
1020 // Will show the cursor at the first character of the selection.
1021 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1025 // Will show the cursor at the last character of the selection.
1026 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1029 mEventData->mDecoratorUpdated = true;
1033 void Controller::Impl::SetSelection(int start, int end)
1035 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1036 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1038 mEventData->mLeftSelectionPosition = start;
1039 mEventData->mRightSelectionPosition = end;
1040 mEventData->mUpdateCursorPosition = true;
1042 if(mSelectableControlInterface != nullptr)
1044 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1048 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1050 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1053 void Controller::Impl::ShowClipboard()
1055 if(EnsureClipboardCreated())
1057 mClipboard.ShowClipboard();
1061 void Controller::Impl::HideClipboard()
1063 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1065 mClipboard.HideClipboard();
1069 void Controller::Impl::SetClipboardHideEnable(bool enable)
1071 mClipboardHideEnabled = enable;
1074 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1076 if(EnsureClipboardCreated())
1078 Dali::Clipboard::ClipData data(MIME_TYPE_TEXT_PLAIN, source.c_str());
1079 return mClipboard.SetData(data); // Send clipboard data to clipboard.
1085 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1087 std::string selectedText;
1088 RetrieveSelection(selectedText, deleteAfterSending);
1089 CopyStringToClipboard(selectedText);
1090 ChangeState(EventData::EDITING);
1093 void Controller::Impl::RepositionSelectionHandles()
1095 SelectionHandleController::Reposition(*this);
1097 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1099 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1102 void Controller::Impl::SetPopupButtons()
1105 * Sets the Popup buttons to be shown depending on State.
1107 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1109 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1112 bool isEditable = IsEditable();
1113 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1115 if(EventData::SELECTING == mEventData->mState)
1117 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1120 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1123 if(!IsClipboardEmpty())
1127 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1129 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1132 if(!mEventData->mAllTextSelected)
1134 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1137 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1139 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1141 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1144 if(!IsClipboardEmpty())
1148 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1150 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1153 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1155 if(!IsClipboardEmpty())
1159 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1161 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1165 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1168 void Controller::Impl::ChangeState(EventData::State newState)
1170 ChangeTextControllerState(*this, newState);
1173 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1174 CursorInfo& cursorInfo)
1176 if(!IsShowingRealText())
1178 // Do not want to use the place-holder text to set the cursor position.
1180 // Use the line's height of the font's family set to set the cursor's size.
1181 // If there is no font's family set, use the default font.
1182 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1184 cursorInfo.lineOffset = 0.f;
1185 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1186 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1189 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1191 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1194 switch(mModel->mHorizontalAlignment)
1196 case Text::HorizontalAlignment::BEGIN:
1200 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1204 cursorInfo.primaryPosition.x = 0.f;
1208 case Text::HorizontalAlignment::CENTER:
1210 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1213 case Text::HorizontalAlignment::END:
1217 cursorInfo.primaryPosition.x = 0.f;
1221 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1227 // Nothing else to do.
1231 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1232 GetCursorPositionParameters parameters;
1233 parameters.visualModel = mModel->mVisualModel;
1234 parameters.logicalModel = mModel->mLogicalModel;
1235 parameters.metrics = mMetrics;
1236 parameters.logical = logical;
1237 parameters.isMultiline = isMultiLine;
1239 float defaultFontLineHeight = GetDefaultFontLineHeight();
1241 Text::GetCursorPosition(parameters,
1242 defaultFontLineHeight,
1245 // Adds Outline offset.
1246 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1247 cursorInfo.primaryPosition.x += outlineWidth;
1248 cursorInfo.primaryPosition.y += outlineWidth;
1249 cursorInfo.secondaryPosition.x += outlineWidth;
1250 cursorInfo.secondaryPosition.y += outlineWidth;
1254 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1256 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1257 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1259 if(0.f > cursorInfo.primaryPosition.x)
1261 cursorInfo.primaryPosition.x = 0.f;
1264 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1265 if(cursorInfo.primaryPosition.x > edgeWidth)
1267 cursorInfo.primaryPosition.x = edgeWidth;
1272 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1274 if(nullptr == mEventData)
1276 // Nothing to do if there is no text input.
1280 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1282 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1283 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1285 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1286 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1288 if(numberOfCharacters > 1u)
1290 const Script script = mModel->mLogicalModel->GetScript(index);
1291 if(HasLigatureMustBreak(script))
1293 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1294 numberOfCharacters = 1u;
1299 while(0u == numberOfCharacters)
1302 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1306 if(index < mEventData->mPrimaryCursorPosition)
1308 cursorIndex -= numberOfCharacters;
1312 cursorIndex += numberOfCharacters;
1315 // Will update the cursor hook position.
1316 mEventData->mUpdateCursorHookPosition = true;
1321 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1323 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1324 if(nullptr == mEventData)
1326 // Nothing to do if there is no text input.
1327 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1331 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1333 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1335 // Sets the cursor position.
1336 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1339 cursorInfo.primaryCursorHeight,
1340 cursorInfo.lineHeight);
1341 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1343 if(mEventData->mUpdateGrabHandlePosition)
1345 // Sets the grab handle position.
1346 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1348 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1349 cursorInfo.lineHeight);
1352 if(cursorInfo.isSecondaryCursor)
1354 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1355 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1356 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1357 cursorInfo.secondaryCursorHeight,
1358 cursorInfo.lineHeight);
1359 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1362 // Set which cursors are active according the state.
1363 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1365 if(cursorInfo.isSecondaryCursor)
1367 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1371 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1376 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1379 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1382 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1383 const CursorInfo& cursorInfo)
1385 SelectionHandleController::Update(*this, handleType, cursorInfo);
1388 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1390 // Clamp between -space & -alignment offset.
1392 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1394 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1395 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1396 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1398 mEventData->mDecoratorUpdated = true;
1402 mModel->mScrollPosition.x = 0.f;
1406 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1408 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1410 // Nothing to do if the text is single line.
1414 // Clamp between -space & 0.
1415 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1417 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1418 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1419 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1421 mEventData->mDecoratorUpdated = true;
1425 mModel->mScrollPosition.y = 0.f;
1429 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1431 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1433 // position is in actor's coords.
1434 const float positionEndX = position.x + cursorWidth;
1435 const float positionEndY = position.y + lineHeight;
1437 // Transform the position to decorator coords.
1438 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1439 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1441 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1442 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1444 if(decoratorPositionBeginX < 0.f)
1446 mModel->mScrollPosition.x = -position.x;
1448 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1450 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1453 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1455 if(decoratorPositionBeginY < 0.f)
1457 mModel->mScrollPosition.y = -position.y;
1459 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1461 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1463 else if(mModel->mLogicalModel->mText.Count() == 0u)
1465 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1470 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1472 // Get the current cursor position in decorator coords.
1473 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1475 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1477 // Calculate the offset to match the cursor position before the character was deleted.
1478 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1480 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1481 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1483 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1484 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1487 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1488 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1490 // Makes the new cursor position visible if needed.
1491 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1494 void Controller::Impl::ScrollTextToMatchCursor()
1496 CursorInfo cursorInfo;
1497 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1498 ScrollTextToMatchCursor(cursorInfo);
1501 void Controller::Impl::RequestRelayout()
1503 if(nullptr != mControlInterface)
1505 mControlInterface->RequestTextRelayout();
1509 void Controller::Impl::RelayoutAllCharacters()
1511 // relayout all characters
1512 mTextUpdateInfo.mCharacterIndex = 0;
1513 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1514 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1515 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1517 mTextUpdateInfo.mFullRelayoutNeeded = true;
1519 // Need to recalculate natural size
1520 mRecalculateNaturalSize = true;
1523 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1525 ChangeState(EventData::EDITING);
1531 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1533 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1536 void Controller::Impl::ProcessInputStyleChangedSignals()
1540 if(mEditableControlInterface)
1542 // Emit the input style changed signal for each mask
1543 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1544 mEventData->mInputStyleChangedQueue.end(),
1545 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1548 mEventData->mInputStyleChangedQueue.Clear();
1552 void Controller::Impl::ScrollBy(Vector2 scroll)
1554 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1556 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1557 const Vector2 currentScroll = mModel->mScrollPosition;
1559 scroll.x = -scroll.x;
1560 scroll.y = -scroll.y;
1562 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1564 mModel->mScrollPosition.x += scroll.x;
1565 ClampHorizontalScroll(layoutSize);
1568 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1570 mModel->mScrollPosition.y += scroll.y;
1571 ClampVerticalScroll(layoutSize);
1574 if(mModel->mScrollPosition != currentScroll)
1576 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1582 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1584 bool isScrollable = false;
1587 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1588 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1589 if(isHorizontalScrollEnabled || isVerticalScrollEnabled)
1591 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1592 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1593 const Vector2& scrollPosition = mModel->mScrollPosition;
1595 if(isHorizontalScrollEnabled)
1597 const float displacementX = displacement.x;
1598 const float positionX = scrollPosition.x + displacementX;
1599 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1601 isScrollable = true;
1605 if(isVerticalScrollEnabled)
1607 const float displacementY = displacement.y;
1608 const float positionY = scrollPosition.y + displacementY;
1609 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1611 isScrollable = true;
1616 return isScrollable;
1619 float Controller::Impl::GetHorizontalScrollPosition()
1621 // Scroll values are negative internally so we convert them to positive numbers
1622 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1625 float Controller::Impl::GetVerticalScrollPosition()
1627 // Scroll values are negative internally so we convert them to positive numbers
1628 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1631 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1634 return Vector3(10.f, 10.f, 10.f);
1637 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1640 return Vector2(10.f, 10.f);
1643 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1645 auto actor = Toolkit::TextAnchor::New();
1646 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1647 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1648 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1649 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1650 const Vector2 anchorSize = GetAnchorSize(anchor);
1651 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1652 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1653 actor.SetProperty(Actor::Property::NAME, anchorText);
1654 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1655 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1656 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1660 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1662 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1663 we need to create and destroy potentially many actors. Some optimization can be considered here.
1664 Maybe a "dirty" flag in mLogicalModel? */
1665 anchorActors.clear();
1666 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1668 auto actor = CreateAnchorActor(anchor);
1669 anchorActors.push_back(actor);
1673 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1675 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1677 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1682 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1685 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1687 //Underlined character runs for markup-processor
1688 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1689 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1690 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1692 if(shouldClearPreUnderlineRuns)
1694 mModel->mVisualModel->mUnderlineRuns.Clear();
1697 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1699 CharacterIndex characterIndex = it->characterRun.characterIndex;
1700 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1702 if(numberOfCharacters == 0)
1707 // Create one run for all glyphs of all run's characters that has same properties
1708 // This enhance performance and reduce the needed memory to store glyphs-runs
1709 UnderlinedGlyphRun underlineGlyphRun;
1710 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1711 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1712 //Copy properties (attributes)
1713 underlineGlyphRun.properties = it->properties;
1715 for(Length index = 1u; index < numberOfCharacters; index++)
1717 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1720 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1723 // Reset flag. The updates have been applied from logical to visual.
1724 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1727 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1729 //Strikethrough character runs from markup-processor
1730 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1731 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1732 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1734 mModel->mVisualModel->mStrikethroughRuns.Clear();
1736 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1738 CharacterIndex characterIndex = it->characterRun.characterIndex;
1739 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1741 if(numberOfCharacters == 0)
1746 StrikethroughGlyphRun strikethroughGlyphRun;
1747 strikethroughGlyphRun.properties = it->properties;
1748 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1749 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1751 for(Length index = 1u; index < numberOfCharacters; index++)
1753 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1756 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1759 // Reset flag. The updates have been applied from logical to visual.
1760 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1763 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1765 //CharacterSpacing character runs from markup-processor
1766 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1767 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1768 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1770 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1772 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1774 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1775 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1777 if(numberOfCharacters == 0)
1782 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1783 characterSpacingGlyphRun.value = it->value;
1784 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1785 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1787 for(Length index = 1u; index < numberOfCharacters; index++)
1789 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1792 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1794 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1797 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1799 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1801 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1804 UPDATE_LAYOUT_SIZE |
1809 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1810 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1814 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1817 mIsAutoScrollEnabled = enable;
1822 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1823 mIsAutoScrollEnabled = false;
1827 void Controller::Impl::SetEnableCursorBlink(bool enable)
1829 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1833 mEventData->mCursorBlinkEnabled = enable;
1835 if(!enable && mEventData->mDecorator)
1837 mEventData->mDecorator->StopCursorBlink();
1842 void Controller::Impl::SetMultiLineEnabled(bool enable)
1844 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1846 if(layout != mLayoutEngine.GetLayout())
1848 // Set the layout type.
1849 mLayoutEngine.SetLayout(layout);
1851 // Set the flags to redo the layout operations
1852 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1853 UPDATE_LAYOUT_SIZE |
1857 mTextUpdateInfo.mFullRelayoutNeeded = true;
1858 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1860 // Need to recalculate natural size
1861 mRecalculateNaturalSize = true;
1867 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1869 if(alignment != mModel->mHorizontalAlignment)
1871 // Set the alignment.
1872 mModel->mHorizontalAlignment = alignment;
1873 UpdateCursorPositionForAlignment(*this, true);
1878 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1880 if(alignment != mModel->mVerticalAlignment)
1882 // Set the alignment.
1883 mModel->mVerticalAlignment = alignment;
1884 UpdateCursorPositionForAlignment(*this, false);
1889 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1891 if(lineWrapMode != mModel->mLineWrapMode)
1893 // Update Text layout for applying wrap mode
1894 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1897 UPDATE_LAYOUT_SIZE |
1900 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1901 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1903 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1906 // Set the text wrap mode.
1907 mModel->mLineWrapMode = lineWrapMode;
1909 mTextUpdateInfo.mCharacterIndex = 0u;
1910 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1911 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1918 void Controller::Impl::SetDefaultColor(const Vector4& color)
1922 if(!IsShowingPlaceholderText())
1924 mModel->mVisualModel->SetTextColor(color);
1925 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1930 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1932 mIsUserInteractionEnabled = enabled;
1934 if(mEventData && mEventData->mDecorator)
1936 bool editable = mEventData->mEditingEnabled && enabled;
1937 mEventData->mDecorator->SetEditable(editable);
1941 void Controller::Impl::ClearFontData()
1945 mFontDefaults->mFontId = 0u; // Remove old font ID
1948 // Set flags to update the model.
1949 mTextUpdateInfo.mCharacterIndex = 0u;
1950 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1951 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1953 mTextUpdateInfo.mClearAll = true;
1954 mTextUpdateInfo.mFullRelayoutNeeded = true;
1955 mRecalculateNaturalSize = true;
1957 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1963 UPDATE_LAYOUT_SIZE |
1968 void Controller::Impl::ClearStyleData()
1970 mModel->mLogicalModel->mColorRuns.Clear();
1971 mModel->mLogicalModel->ClearFontDescriptionRuns();
1972 mModel->mLogicalModel->ClearStrikethroughRuns();
1975 void Controller::Impl::ResetScrollPosition()
1979 // Reset the scroll position.
1980 mModel->mScrollPosition = Vector2::ZERO;
1981 mEventData->mScrollAfterUpdatePosition = true;
1985 } // namespace Dali::Toolkit::Text