2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/rendering/renderer.h>
25 #include <dali/devel-api/adaptor-framework/window-devel.h>
28 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
29 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
30 #include <dali-toolkit/internal/text/character-set-conversion.h>
31 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
32 #include <dali-toolkit/internal/text/text-control-interface.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
34 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
36 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
37 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
38 #include <dali-toolkit/internal/text/text-run-container.h>
39 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
45 #if defined(DEBUG_ENABLED)
46 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
49 struct BackgroundVertex
51 Vector2 mPosition; ///< Vertex posiiton
52 Vector4 mColor; ///< Vertex color
57 Vector<BackgroundVertex> mVertices; ///< container of vertices
58 Vector<unsigned short> mIndices; ///< container of indices
63 namespace Dali::Toolkit::Text
69 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
71 // Sets the default text's color.
72 inputStyle.textColor = textColor;
73 inputStyle.isDefaultColor = true;
75 inputStyle.familyName.clear();
76 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
77 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
78 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
79 inputStyle.size = 0.f;
81 inputStyle.lineSpacing = 0.f;
83 inputStyle.underlineProperties.clear();
84 inputStyle.shadowProperties.clear();
85 inputStyle.embossProperties.clear();
86 inputStyle.outlineProperties.clear();
88 inputStyle.isFamilyDefined = false;
89 inputStyle.isWeightDefined = false;
90 inputStyle.isWidthDefined = false;
91 inputStyle.isSlantDefined = false;
92 inputStyle.isSizeDefined = false;
94 inputStyle.isLineSpacingDefined = false;
96 inputStyle.isUnderlineDefined = false;
97 inputStyle.isShadowDefined = false;
98 inputStyle.isEmbossDefined = false;
99 inputStyle.isOutlineDefined = false;
101 // Sets the default font's family name, weight, width, slant and size.
104 if(fontDefaults->familyDefined)
106 inputStyle.familyName = fontDefaults->mFontDescription.family;
107 inputStyle.isFamilyDefined = true;
110 if(fontDefaults->weightDefined)
112 inputStyle.weight = fontDefaults->mFontDescription.weight;
113 inputStyle.isWeightDefined = true;
116 if(fontDefaults->widthDefined)
118 inputStyle.width = fontDefaults->mFontDescription.width;
119 inputStyle.isWidthDefined = true;
122 if(fontDefaults->slantDefined)
124 inputStyle.slant = fontDefaults->mFontDescription.slant;
125 inputStyle.isSlantDefined = true;
128 if(fontDefaults->sizeDefined)
130 inputStyle.size = fontDefaults->mDefaultPointSize;
131 inputStyle.isSizeDefined = true;
136 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
138 EventData* eventData = impl.mEventData;
140 if(nullptr == eventData)
142 // Nothing to do if there is no text input.
146 DecoratorPtr& decorator = eventData->mDecorator;
149 // Nothing to do if there is no decorator.
153 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
155 if(eventData->mState != newState)
157 eventData->mPreviousState = eventData->mState;
158 eventData->mState = newState;
160 switch(eventData->mState)
162 case EventData::INACTIVE:
164 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
165 decorator->StopCursorBlink();
166 decorator->SetHandleActive(GRAB_HANDLE, false);
167 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
168 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
169 decorator->SetHighlightActive(false);
170 decorator->SetPopupActive(false);
171 eventData->mDecoratorUpdated = true;
175 case EventData::INTERRUPTED:
177 decorator->SetHandleActive(GRAB_HANDLE, false);
178 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
179 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
180 decorator->SetHighlightActive(false);
181 decorator->SetPopupActive(false);
182 eventData->mDecoratorUpdated = true;
186 case EventData::SELECTING:
188 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
189 decorator->StopCursorBlink();
190 decorator->SetHandleActive(GRAB_HANDLE, false);
191 if(eventData->mGrabHandleEnabled)
193 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
194 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
196 decorator->SetHighlightActive(true);
197 if(eventData->mGrabHandlePopupEnabled)
199 impl.SetPopupButtons();
200 decorator->SetPopupActive(true);
202 eventData->mDecoratorUpdated = true;
206 case EventData::EDITING:
208 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
209 if(eventData->mCursorBlinkEnabled)
211 decorator->StartCursorBlink();
213 // Grab handle is not shown until a tap is received whilst EDITING
214 decorator->SetHandleActive(GRAB_HANDLE, false);
215 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
216 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
217 decorator->SetHighlightActive(false);
218 if(eventData->mGrabHandlePopupEnabled)
220 decorator->SetPopupActive(false);
222 eventData->mDecoratorUpdated = true;
225 case EventData::EDITING_WITH_POPUP:
227 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
229 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
230 if(eventData->mCursorBlinkEnabled)
232 decorator->StartCursorBlink();
234 if(eventData->mSelectionEnabled)
236 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
237 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
238 decorator->SetHighlightActive(false);
240 else if(eventData->mGrabHandleEnabled)
242 decorator->SetHandleActive(GRAB_HANDLE, true);
244 if(eventData->mGrabHandlePopupEnabled)
246 impl.SetPopupButtons();
247 decorator->SetPopupActive(true);
249 eventData->mDecoratorUpdated = true;
252 case EventData::EDITING_WITH_GRAB_HANDLE:
254 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
256 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
257 if(eventData->mCursorBlinkEnabled)
259 decorator->StartCursorBlink();
261 // Grab handle is not shown until a tap is received whilst EDITING
262 if(eventData->mGrabHandleEnabled)
264 decorator->SetHandleActive(GRAB_HANDLE, true);
266 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
267 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
268 decorator->SetHighlightActive(false);
269 if(eventData->mGrabHandlePopupEnabled)
271 decorator->SetPopupActive(false);
273 eventData->mDecoratorUpdated = true;
277 case EventData::SELECTION_HANDLE_PANNING:
279 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
280 decorator->StopCursorBlink();
281 decorator->SetHandleActive(GRAB_HANDLE, false);
282 if(eventData->mGrabHandleEnabled)
284 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
285 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
287 decorator->SetHighlightActive(true);
288 if(eventData->mGrabHandlePopupEnabled)
290 decorator->SetPopupActive(false);
292 eventData->mDecoratorUpdated = true;
296 case EventData::GRAB_HANDLE_PANNING:
298 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
300 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
301 if(eventData->mCursorBlinkEnabled)
303 decorator->StartCursorBlink();
305 if(eventData->mGrabHandleEnabled)
307 decorator->SetHandleActive(GRAB_HANDLE, true);
309 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
310 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
311 decorator->SetHighlightActive(false);
312 if(eventData->mGrabHandlePopupEnabled)
314 decorator->SetPopupActive(false);
316 eventData->mDecoratorUpdated = true;
320 case EventData::EDITING_WITH_PASTE_POPUP:
322 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
324 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
325 if(eventData->mCursorBlinkEnabled)
327 decorator->StartCursorBlink();
330 if(eventData->mGrabHandleEnabled)
332 decorator->SetHandleActive(GRAB_HANDLE, true);
334 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
335 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
336 decorator->SetHighlightActive(false);
338 if(eventData->mGrabHandlePopupEnabled)
340 impl.SetPopupButtons();
341 decorator->SetPopupActive(true);
343 eventData->mDecoratorUpdated = true;
347 case EventData::TEXT_PANNING:
349 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
350 decorator->StopCursorBlink();
351 decorator->SetHandleActive(GRAB_HANDLE, false);
352 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
353 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
355 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
356 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
357 decorator->SetHighlightActive(true);
360 if(eventData->mGrabHandlePopupEnabled)
362 decorator->SetPopupActive(false);
365 eventData->mDecoratorUpdated = true;
372 } // unnamed Namespace
374 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
375 : mDecorator(decorator),
376 mInputMethodContext(inputMethodContext),
377 mPlaceholderFont(nullptr),
378 mPlaceholderTextActive(),
379 mPlaceholderTextInactive(),
380 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
382 mInputStyleChangedQueue(),
383 mPreviousState(INACTIVE),
385 mPrimaryCursorPosition(0u),
386 mLeftSelectionPosition(0u),
387 mRightSelectionPosition(0u),
388 mPreEditStartPosition(0u),
390 mCursorHookPositionX(0.f),
391 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
392 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
393 mIsShowingPlaceholderText(false),
395 mDecoratorUpdated(false),
396 mCursorBlinkEnabled(true),
397 mGrabHandleEnabled(true),
398 mGrabHandlePopupEnabled(true),
399 mSelectionEnabled(true),
400 mUpdateCursorHookPosition(false),
401 mUpdateCursorPosition(false),
402 mUpdateGrabHandlePosition(false),
403 mUpdateLeftSelectionPosition(false),
404 mUpdateRightSelectionPosition(false),
405 mIsLeftHandleSelected(false),
406 mIsRightHandleSelected(false),
407 mUpdateHighlightBox(false),
408 mScrollAfterUpdatePosition(false),
409 mScrollAfterDelete(false),
410 mAllTextSelected(false),
411 mUpdateInputStyle(false),
412 mPasswordInput(false),
413 mCheckScrollAmount(false),
414 mIsPlaceholderPixelSize(false),
415 mIsPlaceholderElideEnabled(false),
416 mPlaceholderEllipsisFlag(false),
417 mShiftSelectionFlag(true),
418 mUpdateAlignment(false),
419 mEditingEnabled(true)
423 bool Controller::Impl::ProcessInputEvents()
425 return ControllerImplEventHandler::ProcessInputEvents(*this);
428 void Controller::Impl::NotifyInputMethodContext()
430 if(mEventData && mEventData->mInputMethodContext)
432 CharacterIndex cursorPosition = GetLogicalCursorPosition();
434 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
436 // Update the cursor position by removing the initial white spaces.
437 if(cursorPosition < numberOfWhiteSpaces)
443 cursorPosition -= numberOfWhiteSpaces;
446 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
447 mEventData->mInputMethodContext.NotifyCursorPosition();
451 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
453 if(mEventData && mEventData->mInputMethodContext)
455 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
456 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
460 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
462 CharacterIndex cursorPosition = 0u;
466 if((EventData::SELECTING == mEventData->mState) ||
467 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
469 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
473 cursorPosition = mEventData->mPrimaryCursorPosition;
477 return cursorPosition;
480 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
482 Length numberOfWhiteSpaces = 0u;
484 // Get the buffer to the text.
485 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
487 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
488 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
490 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
496 return numberOfWhiteSpaces;
499 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
501 // Get the total number of characters.
502 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
504 // Retrieve the text.
505 if(0u != numberOfCharacters)
507 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
511 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
513 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
514 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
516 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
520 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
524 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
526 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
527 mTextUpdateInfo.mStartGlyphIndex = 0u;
528 mTextUpdateInfo.mStartLineIndex = 0u;
529 numberOfCharacters = 0u;
531 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
532 if(0u == numberOfParagraphs)
534 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
535 numberOfCharacters = 0u;
537 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
539 // Nothing else to do if there are no paragraphs.
543 // Find the paragraphs to be updated.
544 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
545 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
547 // Text is being added at the end of the current text.
548 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
550 // Text is being added in a new paragraph after the last character of the text.
551 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
552 numberOfCharacters = 0u;
553 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
555 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
556 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
558 // Nothing else to do;
562 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
566 Length numberOfCharactersToUpdate = 0u;
567 if(mTextUpdateInfo.mFullRelayoutNeeded)
569 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
573 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
575 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
576 numberOfCharactersToUpdate,
577 paragraphsToBeUpdated);
580 if(0u != paragraphsToBeUpdated.Count())
582 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
583 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
584 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
586 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
587 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
589 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
590 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
591 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
592 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
594 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
595 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
597 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
601 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
605 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
606 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
609 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
611 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
614 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
616 return ControllerImplModelUpdater::Update(*this, operationsRequired);
619 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
621 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
624 float Controller::Impl::GetDefaultFontLineHeight()
626 FontId defaultFontId = 0u;
627 if(nullptr == mFontDefaults)
629 TextAbstraction::FontDescription fontDescription;
630 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale);
634 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
637 Text::FontMetrics fontMetrics;
638 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
640 return (fontMetrics.ascender - fontMetrics.descender);
643 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
645 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
647 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
648 mRecalculateNaturalSize = true;
650 RelayoutForNewLineSize();
656 bool Controller::Impl::SetDefaultLineSize(float lineSize)
658 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
660 mLayoutEngine.SetDefaultLineSize(lineSize);
661 mRecalculateNaturalSize = true;
663 RelayoutForNewLineSize();
669 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
671 if(nullptr == mEventData)
673 // Nothing to do if there is no text.
677 if(mEventData->mSelectionEnabled && (pStart || pEnd))
679 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
680 uint32_t oldStart = mEventData->mLeftSelectionPosition;
681 uint32_t oldEnd = mEventData->mRightSelectionPosition;
685 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
689 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
692 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
694 ChangeState(EventData::EDITING);
695 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
696 mEventData->mUpdateCursorPosition = true;
700 ChangeState(EventData::SELECTING);
701 mEventData->mUpdateHighlightBox = true;
702 mEventData->mUpdateLeftSelectionPosition = true;
703 mEventData->mUpdateRightSelectionPosition = true;
706 if(mSelectableControlInterface != nullptr)
708 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
713 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
715 if(nullptr == mEventData)
719 return mEventData->mPrimaryCursorPosition;
722 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
724 if(nullptr == mEventData)
726 // Nothing to do if there is no text.
730 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
732 // Nothing for same cursor position.
736 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
737 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
738 mEventData->mPrimaryCursorPosition = std::min(index, length);
739 // If there is no focus, only the value is updated.
742 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
743 uint32_t oldStart = mEventData->mLeftSelectionPosition;
744 uint32_t oldEnd = mEventData->mRightSelectionPosition;
745 ChangeState(EventData::EDITING);
746 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
747 mEventData->mUpdateCursorPosition = true;
749 if(mSelectableControlInterface != nullptr && wasInSelectingState)
751 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
754 ScrollTextToMatchCursor();
757 if(nullptr != mEditableControlInterface)
759 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
765 Uint32Pair Controller::Impl::GetTextSelectionRange() const
771 range.first = mEventData->mLeftSelectionPosition;
772 range.second = mEventData->mRightSelectionPosition;
778 bool Controller::Impl::IsEditable() const
780 return mEventData && mEventData->mEditingEnabled;
783 void Controller::Impl::SetEditable(bool editable)
787 mEventData->mEditingEnabled = editable;
789 if(mEventData->mDecorator)
791 mEventData->mDecorator->SetEditable(editable);
796 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
798 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
800 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
802 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
803 mFontDefaults->mFontDescription.family = newDefaultFont;
811 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
813 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
815 // Nothing to select if handles are in the same place.
816 selectedText.clear();
820 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
822 //Get start and end position of selection
823 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
824 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
826 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
827 const Length numberOfCharacters = utf32Characters.Count();
829 // Validate the start and end selection points
830 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
832 //Get text as a UTF8 string
833 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
835 if(deleteAfterRetrieval) // Only delete text if copied successfully
837 // Keep a copy of the current input style.
838 InputStyle currentInputStyle;
839 currentInputStyle.Copy(mEventData->mInputStyle);
841 // Set as input style the style of the first deleted character.
842 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
844 // Compare if the input style has changed.
845 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
847 if(hasInputStyleChanged)
849 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
850 // Queue the input style changed signal.
851 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
854 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
856 // Mark the paragraphs to be updated.
857 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
859 mTextUpdateInfo.mCharacterIndex = 0;
860 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
861 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
862 mTextUpdateInfo.mClearAll = true;
866 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
867 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
870 // Delete text between handles
871 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
872 Vector<Character>::Iterator last = first + lengthOfSelectedText;
873 utf32Characters.Erase(first, last);
875 // Will show the cursor at the first character of the selection.
876 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
880 // Will show the cursor at the last character of the selection.
881 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
884 mEventData->mDecoratorUpdated = true;
888 void Controller::Impl::SetSelection(int start, int end)
890 uint32_t oldStart = mEventData->mLeftSelectionPosition;
891 uint32_t oldEnd = mEventData->mRightSelectionPosition;
893 mEventData->mLeftSelectionPosition = start;
894 mEventData->mRightSelectionPosition = end;
895 mEventData->mUpdateCursorPosition = true;
897 if(mSelectableControlInterface != nullptr)
899 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
903 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
905 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
908 void Controller::Impl::ShowClipboard()
912 mClipboard.ShowClipboard();
916 void Controller::Impl::HideClipboard()
918 if(mClipboard && mClipboardHideEnabled)
920 mClipboard.HideClipboard();
924 void Controller::Impl::SetClipboardHideEnable(bool enable)
926 mClipboardHideEnabled = enable;
929 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
931 //Send string to clipboard
932 return (mClipboard && mClipboard.SetItem(source));
935 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
937 std::string selectedText;
938 RetrieveSelection(selectedText, deleteAfterSending);
939 CopyStringToClipboard(selectedText);
940 ChangeState(EventData::EDITING);
943 void Controller::Impl::RequestGetTextFromClipboard()
947 mClipboard.RequestItem();
951 void Controller::Impl::RepositionSelectionHandles()
953 SelectionHandleController::Reposition(*this);
955 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
957 SelectionHandleController::Reposition(*this, visualX, visualY, action);
960 void Controller::Impl::SetPopupButtons()
963 * Sets the Popup buttons to be shown depending on State.
965 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
967 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
970 bool isEditable = IsEditable();
971 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
973 if(EventData::SELECTING == mEventData->mState)
975 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
978 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
981 if(!IsClipboardEmpty())
985 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
987 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
990 if(!mEventData->mAllTextSelected)
992 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
995 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
997 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
999 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1002 if(!IsClipboardEmpty())
1006 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1008 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1011 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1013 if(!IsClipboardEmpty())
1017 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1019 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1023 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1026 void Controller::Impl::ChangeState(EventData::State newState)
1028 ChangeTextControllerState(*this, newState);
1031 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1032 CursorInfo& cursorInfo)
1034 if(!IsShowingRealText())
1036 // Do not want to use the place-holder text to set the cursor position.
1038 // Use the line's height of the font's family set to set the cursor's size.
1039 // If there is no font's family set, use the default font.
1040 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1042 cursorInfo.lineOffset = 0.f;
1043 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1044 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1047 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1049 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1052 switch(mModel->mHorizontalAlignment)
1054 case Text::HorizontalAlignment::BEGIN:
1058 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1062 cursorInfo.primaryPosition.x = 0.f;
1066 case Text::HorizontalAlignment::CENTER:
1068 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1071 case Text::HorizontalAlignment::END:
1075 cursorInfo.primaryPosition.x = 0.f;
1079 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1085 // Nothing else to do.
1089 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1090 GetCursorPositionParameters parameters;
1091 parameters.visualModel = mModel->mVisualModel;
1092 parameters.logicalModel = mModel->mLogicalModel;
1093 parameters.metrics = mMetrics;
1094 parameters.logical = logical;
1095 parameters.isMultiline = isMultiLine;
1097 Text::GetCursorPosition(parameters,
1100 // Adds Outline offset.
1101 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1102 cursorInfo.primaryPosition.x += outlineWidth;
1103 cursorInfo.primaryPosition.y += outlineWidth;
1104 cursorInfo.secondaryPosition.x += outlineWidth;
1105 cursorInfo.secondaryPosition.y += outlineWidth;
1109 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1111 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1112 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1114 if(0.f > cursorInfo.primaryPosition.x)
1116 cursorInfo.primaryPosition.x = 0.f;
1119 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1120 if(cursorInfo.primaryPosition.x > edgeWidth)
1122 cursorInfo.primaryPosition.x = edgeWidth;
1127 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1129 if(nullptr == mEventData)
1131 // Nothing to do if there is no text input.
1135 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1137 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1138 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1140 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1141 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1143 if(numberOfCharacters > 1u)
1145 const Script script = mModel->mLogicalModel->GetScript(index);
1146 if(HasLigatureMustBreak(script))
1148 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1149 numberOfCharacters = 1u;
1154 while(0u == numberOfCharacters)
1157 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1161 if(index < mEventData->mPrimaryCursorPosition)
1163 cursorIndex -= numberOfCharacters;
1167 cursorIndex += numberOfCharacters;
1170 // Will update the cursor hook position.
1171 mEventData->mUpdateCursorHookPosition = true;
1176 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1178 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1179 if(nullptr == mEventData)
1181 // Nothing to do if there is no text input.
1182 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1186 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1188 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1190 // Sets the cursor position.
1191 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1194 cursorInfo.primaryCursorHeight,
1195 cursorInfo.lineHeight);
1196 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1198 if(mEventData->mUpdateGrabHandlePosition)
1200 // Sets the grab handle position.
1201 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1203 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1204 cursorInfo.lineHeight);
1207 if(cursorInfo.isSecondaryCursor)
1209 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1210 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1211 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1212 cursorInfo.secondaryCursorHeight,
1213 cursorInfo.lineHeight);
1214 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1217 // Set which cursors are active according the state.
1218 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1220 if(cursorInfo.isSecondaryCursor)
1222 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1226 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1231 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1234 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1237 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1238 const CursorInfo& cursorInfo)
1240 SelectionHandleController::Update(*this, handleType, cursorInfo);
1243 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1245 // Clamp between -space & -alignment offset.
1247 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1249 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1250 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1251 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1253 mEventData->mDecoratorUpdated = true;
1257 mModel->mScrollPosition.x = 0.f;
1261 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1263 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1265 // Nothing to do if the text is single line.
1269 // Clamp between -space & 0.
1270 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1272 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1273 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1274 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1276 mEventData->mDecoratorUpdated = true;
1280 mModel->mScrollPosition.y = 0.f;
1284 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1286 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1288 // position is in actor's coords.
1289 const float positionEndX = position.x + cursorWidth;
1290 const float positionEndY = position.y + lineHeight;
1292 // Transform the position to decorator coords.
1293 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1294 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1296 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1297 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1299 if(decoratorPositionBeginX < 0.f)
1301 mModel->mScrollPosition.x = -position.x;
1303 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1305 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1308 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1310 if(decoratorPositionBeginY < 0.f)
1312 mModel->mScrollPosition.y = -position.y;
1314 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1316 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1321 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1323 // Get the current cursor position in decorator coords.
1324 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1326 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1328 // Calculate the offset to match the cursor position before the character was deleted.
1329 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1331 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1332 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1334 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1335 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1338 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1339 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1341 // Makes the new cursor position visible if needed.
1342 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1345 void Controller::Impl::ScrollTextToMatchCursor()
1347 CursorInfo cursorInfo;
1348 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1349 ScrollTextToMatchCursor(cursorInfo);
1352 void Controller::Impl::RequestRelayout()
1354 if(nullptr != mControlInterface)
1356 mControlInterface->RequestTextRelayout();
1360 Actor Controller::Impl::CreateBackgroundActor()
1362 // NOTE: Currently we only support background color for left-to-right text.
1366 Length numberOfGlyphs = mView.GetNumberOfGlyphs();
1367 if(numberOfGlyphs > 0u)
1369 Vector<GlyphInfo> glyphs;
1370 glyphs.Resize(numberOfGlyphs);
1372 Vector<Vector2> positions;
1373 positions.Resize(numberOfGlyphs);
1375 // Get the line where the glyphs are laid-out.
1376 const LineRun* lineRun = mModel->mVisualModel->mLines.Begin();
1377 float alignmentOffset = lineRun->alignmentOffset;
1378 numberOfGlyphs = mView.GetGlyphs(glyphs.Begin(),
1384 glyphs.Resize(numberOfGlyphs);
1385 positions.Resize(numberOfGlyphs);
1387 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
1388 const Vector2* const positionsBuffer = positions.Begin();
1390 BackgroundMesh mesh;
1391 mesh.mVertices.Reserve(4u * glyphs.Size());
1392 mesh.mIndices.Reserve(6u * glyphs.Size());
1394 const Vector2 textSize = mView.GetLayoutSize();
1396 const float offsetX = alignmentOffset + textSize.width * 0.5f;
1397 const float offsetY = textSize.height * 0.5f;
1399 const Vector4* const backgroundColorsBuffer = mView.GetBackgroundColors();
1400 const ColorIndex* const backgroundColorIndicesBuffer = mView.GetBackgroundColorIndices();
1401 const Vector4& defaultBackgroundColor = mModel->mVisualModel->IsBackgroundEnabled() ? mModel->mVisualModel->GetBackgroundColor() : Color::TRANSPARENT;
1404 uint32_t numberOfQuads = 0u;
1405 Length yLineOffset = 0;
1406 Length prevLineIndex = 0;
1407 LineIndex lineIndex;
1408 Length numberOfLines;
1410 for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
1412 const GlyphInfo& glyph = *(glyphsBuffer + i);
1414 // Get the background color of the character.
1415 // The color index zero is reserved for the default background color (i.e. Color::TRANSPARENT)
1416 const bool isMarkupBackground = mView.IsMarkupBackgroundColorSet();
1417 const ColorIndex backgroundColorIndex = isMarkupBackground ? *(backgroundColorIndicesBuffer + i) : 0u;
1418 const bool isDefaultBackgroundColor = (0u == backgroundColorIndex);
1419 const Vector4& backgroundColor = isDefaultBackgroundColor ? defaultBackgroundColor : *(backgroundColorsBuffer + backgroundColorIndex - 1u);
1421 mModel->mVisualModel->GetNumberOfLines(i, 1, lineIndex, numberOfLines);
1422 Length lineHeight = lineRun[lineIndex].ascender + -(lineRun[lineIndex].descender) + lineRun[lineIndex].lineSpacing;
1424 if(lineIndex != prevLineIndex)
1426 yLineOffset += lineHeight;
1429 // Only create quads for glyphs with a background color
1430 if(backgroundColor != Color::TRANSPARENT)
1432 const Vector2 position = *(positionsBuffer + i);
1434 if(i == 0u && glyphSize == 1u) // Only one glyph in the whole text
1436 quad.x = position.x;
1437 quad.y = yLineOffset;
1438 quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
1439 quad.w = lineHeight;
1441 else if((lineIndex != prevLineIndex) || (i == 0u)) // The first glyph in the line
1443 quad.x = position.x;
1444 quad.y = yLineOffset;
1445 quad.z = quad.x - glyph.xBearing + glyph.advance;
1446 quad.w = quad.y + lineHeight;
1448 else if(i == glyphSize - 1u) // The last glyph in the whole text
1450 quad.x = position.x - glyph.xBearing;
1451 quad.y = yLineOffset;
1452 quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width);
1453 quad.w = quad.y + lineHeight;
1455 else // The glyph in the middle of the text
1457 quad.x = position.x - glyph.xBearing;
1458 quad.y = yLineOffset;
1459 quad.z = quad.x + glyph.advance;
1460 quad.w = quad.y + lineHeight;
1463 BackgroundVertex vertex;
1466 vertex.mPosition.x = quad.x - offsetX;
1467 vertex.mPosition.y = quad.y - offsetY;
1468 vertex.mColor = backgroundColor;
1469 mesh.mVertices.PushBack(vertex);
1472 vertex.mPosition.x = quad.z - offsetX;
1473 vertex.mPosition.y = quad.y - offsetY;
1474 vertex.mColor = backgroundColor;
1475 mesh.mVertices.PushBack(vertex);
1478 vertex.mPosition.x = quad.x - offsetX;
1479 vertex.mPosition.y = quad.w - offsetY;
1480 vertex.mColor = backgroundColor;
1481 mesh.mVertices.PushBack(vertex);
1484 vertex.mPosition.x = quad.z - offsetX;
1485 vertex.mPosition.y = quad.w - offsetY;
1486 vertex.mColor = backgroundColor;
1487 mesh.mVertices.PushBack(vertex);
1489 // Six indices in counter clockwise winding
1490 mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
1491 mesh.mIndices.PushBack(0u + 4 * numberOfQuads);
1492 mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
1493 mesh.mIndices.PushBack(2u + 4 * numberOfQuads);
1494 mesh.mIndices.PushBack(3u + 4 * numberOfQuads);
1495 mesh.mIndices.PushBack(1u + 4 * numberOfQuads);
1500 if(lineIndex != prevLineIndex)
1502 prevLineIndex = lineIndex;
1506 // Only create the background actor if there are glyphs with background color
1507 if(mesh.mVertices.Count() > 0u)
1509 Property::Map quadVertexFormat;
1510 quadVertexFormat["aPosition"] = Property::VECTOR2;
1511 quadVertexFormat["aColor"] = Property::VECTOR4;
1513 VertexBuffer quadVertices = VertexBuffer::New(quadVertexFormat);
1514 quadVertices.SetData(&mesh.mVertices[0], mesh.mVertices.Size());
1516 Geometry quadGeometry = Geometry::New();
1517 quadGeometry.AddVertexBuffer(quadVertices);
1518 quadGeometry.SetIndexBuffer(&mesh.mIndices[0], mesh.mIndices.Size());
1520 if(!mShaderBackground)
1522 mShaderBackground = Shader::New(SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_VERT, SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_FRAG);
1525 Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, mShaderBackground);
1526 renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
1527 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT);
1529 actor = Actor::New();
1530 actor.SetProperty(Dali::Actor::Property::NAME, "TextBackgroundColorActor");
1531 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1532 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1533 actor.SetProperty(Actor::Property::SIZE, textSize);
1534 actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
1535 actor.AddRenderer(renderer);
1542 void Controller::Impl::RelayoutForNewLineSize()
1544 // relayout all characters
1545 mTextUpdateInfo.mCharacterIndex = 0;
1546 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1547 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1548 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1551 if(mEventData && mEventData->mState == EventData::SELECTING)
1553 ChangeState(EventData::EDITING);
1559 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1561 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1564 void Controller::Impl::ProcessInputStyleChangedSignals()
1568 if(mEditableControlInterface)
1570 // Emit the input style changed signal for each mask
1571 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1572 mEventData->mInputStyleChangedQueue.end(),
1573 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); } );
1576 mEventData->mInputStyleChangedQueue.Clear();
1580 void Controller::Impl::ScrollBy(Vector2 scroll)
1582 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1584 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1585 const Vector2 currentScroll = mModel->mScrollPosition;
1587 scroll.x = -scroll.x;
1588 scroll.y = -scroll.y;
1590 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1592 mModel->mScrollPosition.x += scroll.x;
1593 ClampHorizontalScroll(layoutSize);
1596 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1598 mModel->mScrollPosition.y += scroll.y;
1599 ClampVerticalScroll(layoutSize);
1602 if(mModel->mScrollPosition != currentScroll)
1604 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1610 float Controller::Impl::GetHorizontalScrollPosition()
1612 // Scroll values are negative internally so we convert them to positive numbers
1613 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1616 float Controller::Impl::GetVerticalScrollPosition()
1618 // Scroll values are negative internally so we convert them to positive numbers
1619 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1622 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1624 //Underlined character runs for markup-processor
1625 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1626 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1627 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1629 if(shouldClearPreUnderlineRuns)
1631 mModel->mVisualModel->mUnderlineRuns.Clear();
1634 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1636 CharacterIndex characterIndex = it->characterRun.characterIndex;
1637 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1638 for(Length index = 0u; index < numberOfCharacters; index++)
1640 GlyphRun underlineGlyphRun;
1641 underlineGlyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1642 underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1643 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1648 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1650 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1652 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1655 UPDATE_LAYOUT_SIZE |
1660 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1661 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1665 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1668 mIsAutoScrollEnabled = enable;
1673 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1674 mIsAutoScrollEnabled = false;
1678 void Controller::Impl::SetEnableCursorBlink(bool enable)
1680 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1684 mEventData->mCursorBlinkEnabled = enable;
1686 if(!enable && mEventData->mDecorator)
1688 mEventData->mDecorator->StopCursorBlink();
1693 void Controller::Impl::SetMultiLineEnabled(bool enable)
1695 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1697 if(layout != mLayoutEngine.GetLayout())
1699 // Set the layout type.
1700 mLayoutEngine.SetLayout(layout);
1702 // Set the flags to redo the layout operations
1703 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1704 UPDATE_LAYOUT_SIZE |
1708 mTextUpdateInfo.mFullRelayoutNeeded = true;
1709 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1711 // Need to recalculate natural size
1712 mRecalculateNaturalSize = true;
1718 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1720 if(alignment != mModel->mHorizontalAlignment)
1722 // Set the alignment.
1723 mModel->mHorizontalAlignment = alignment;
1725 // Set the flag to redo the alignment operation.
1726 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1730 mEventData->mUpdateAlignment = true;
1732 // Update the cursor if it's in editing mode
1733 if(EventData::IsEditingState(mEventData->mState))
1735 ChangeState(EventData::EDITING);
1736 mEventData->mUpdateCursorPosition = true;
1744 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1746 if(alignment != mModel->mVerticalAlignment)
1748 // Set the alignment.
1749 mModel->mVerticalAlignment = alignment;
1750 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1755 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1757 if(lineWrapMode != mModel->mLineWrapMode)
1759 // Update Text layout for applying wrap mode
1760 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1763 UPDATE_LAYOUT_SIZE |
1766 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1767 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1769 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1772 // Set the text wrap mode.
1773 mModel->mLineWrapMode = lineWrapMode;
1775 mTextUpdateInfo.mCharacterIndex = 0u;
1776 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1777 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1784 void Controller::Impl::SetDefaultColor(const Vector4& color)
1788 if(!IsShowingPlaceholderText())
1790 mModel->mVisualModel->SetTextColor(color);
1791 mModel->mLogicalModel->mColorRuns.Clear();
1792 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1797 void Controller::Impl::ClearFontData()
1801 mFontDefaults->mFontId = 0u; // Remove old font ID
1804 // Set flags to update the model.
1805 mTextUpdateInfo.mCharacterIndex = 0u;
1806 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1807 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1809 mTextUpdateInfo.mClearAll = true;
1810 mTextUpdateInfo.mFullRelayoutNeeded = true;
1811 mRecalculateNaturalSize = true;
1813 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1819 UPDATE_LAYOUT_SIZE |
1824 void Controller::Impl::ClearStyleData()
1826 mModel->mLogicalModel->mColorRuns.Clear();
1827 mModel->mLogicalModel->ClearFontDescriptionRuns();
1831 void Controller::Impl::ResetScrollPosition()
1835 // Reset the scroll position.
1836 mModel->mScrollPosition = Vector2::ZERO;
1837 mEventData->mScrollAfterUpdatePosition = true;
1841 } // namespace Dali::Toolkit::Text