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/actors/layer.h>
25 #include <dali/devel-api/adaptor-framework/window-devel.h>
28 #include <dali-toolkit/internal/text/character-set-conversion.h>
29 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
30 #include <dali-toolkit/internal/text/text-control-interface.h>
31 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
32 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
34 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-relayouter.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 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
51 const std::string EMPTY_STRING("");
55 namespace Dali::Toolkit::Text
61 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
63 // Sets the default text's color.
64 inputStyle.textColor = textColor;
65 inputStyle.isDefaultColor = true;
67 inputStyle.familyName.clear();
68 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
69 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
70 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
71 inputStyle.size = 0.f;
73 inputStyle.lineSpacing = 0.f;
75 inputStyle.underlineProperties.clear();
76 inputStyle.shadowProperties.clear();
77 inputStyle.embossProperties.clear();
78 inputStyle.outlineProperties.clear();
80 inputStyle.isFamilyDefined = false;
81 inputStyle.isWeightDefined = false;
82 inputStyle.isWidthDefined = false;
83 inputStyle.isSlantDefined = false;
84 inputStyle.isSizeDefined = false;
86 inputStyle.isLineSpacingDefined = false;
88 inputStyle.isUnderlineDefined = false;
89 inputStyle.isShadowDefined = false;
90 inputStyle.isEmbossDefined = false;
91 inputStyle.isOutlineDefined = false;
93 // Sets the default font's family name, weight, width, slant and size.
96 if(fontDefaults->familyDefined)
98 inputStyle.familyName = fontDefaults->mFontDescription.family;
99 inputStyle.isFamilyDefined = true;
102 if(fontDefaults->weightDefined)
104 inputStyle.weight = fontDefaults->mFontDescription.weight;
105 inputStyle.isWeightDefined = true;
108 if(fontDefaults->widthDefined)
110 inputStyle.width = fontDefaults->mFontDescription.width;
111 inputStyle.isWidthDefined = true;
114 if(fontDefaults->slantDefined)
116 inputStyle.slant = fontDefaults->mFontDescription.slant;
117 inputStyle.isSlantDefined = true;
120 if(fontDefaults->sizeDefined)
122 inputStyle.size = fontDefaults->mDefaultPointSize;
123 inputStyle.isSizeDefined = true;
128 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
130 EventData* eventData = impl.mEventData;
132 if(nullptr == eventData)
134 // Nothing to do if there is no text input.
138 DecoratorPtr& decorator = eventData->mDecorator;
141 // Nothing to do if there is no decorator.
145 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
147 if(eventData->mState != newState)
149 eventData->mPreviousState = eventData->mState;
150 eventData->mState = newState;
152 switch(eventData->mState)
154 case EventData::INACTIVE:
156 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
157 decorator->StopCursorBlink();
158 decorator->SetHandleActive(GRAB_HANDLE, false);
159 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
160 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
161 decorator->SetHighlightActive(false);
162 decorator->SetPopupActive(false);
163 eventData->mDecoratorUpdated = true;
167 case EventData::INTERRUPTED:
169 decorator->SetHandleActive(GRAB_HANDLE, false);
170 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
171 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
172 decorator->SetHighlightActive(false);
173 decorator->SetPopupActive(false);
174 eventData->mDecoratorUpdated = true;
178 case EventData::SELECTING:
180 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
181 decorator->StopCursorBlink();
182 decorator->SetHandleActive(GRAB_HANDLE, false);
183 if(eventData->mGrabHandleEnabled)
185 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
186 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
188 decorator->SetHighlightActive(true);
189 if(eventData->mGrabHandlePopupEnabled)
191 impl.SetPopupButtons();
192 decorator->SetPopupActive(true);
194 eventData->mDecoratorUpdated = true;
198 case EventData::EDITING:
200 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
201 if(eventData->mCursorBlinkEnabled)
203 decorator->StartCursorBlink();
205 // Grab handle is not shown until a tap is received whilst EDITING
206 decorator->SetHandleActive(GRAB_HANDLE, false);
207 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
208 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
209 decorator->SetHighlightActive(false);
210 if(eventData->mGrabHandlePopupEnabled)
212 decorator->SetPopupActive(false);
214 eventData->mDecoratorUpdated = true;
217 case EventData::EDITING_WITH_POPUP:
219 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
221 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
222 if(eventData->mCursorBlinkEnabled)
224 decorator->StartCursorBlink();
226 if(eventData->mSelectionEnabled)
228 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
229 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
230 decorator->SetHighlightActive(false);
232 else if(eventData->mGrabHandleEnabled)
234 decorator->SetHandleActive(GRAB_HANDLE, true);
236 if(eventData->mGrabHandlePopupEnabled)
238 impl.SetPopupButtons();
239 decorator->SetPopupActive(true);
241 eventData->mDecoratorUpdated = true;
244 case EventData::EDITING_WITH_GRAB_HANDLE:
246 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
248 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
249 if(eventData->mCursorBlinkEnabled)
251 decorator->StartCursorBlink();
253 // Grab handle is not shown until a tap is received whilst EDITING
254 if(eventData->mGrabHandleEnabled)
256 decorator->SetHandleActive(GRAB_HANDLE, true);
258 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
259 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
260 decorator->SetHighlightActive(false);
261 if(eventData->mGrabHandlePopupEnabled)
263 decorator->SetPopupActive(false);
265 eventData->mDecoratorUpdated = true;
269 case EventData::SELECTION_HANDLE_PANNING:
271 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
272 decorator->StopCursorBlink();
273 decorator->SetHandleActive(GRAB_HANDLE, false);
274 if(eventData->mGrabHandleEnabled)
276 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
277 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
279 decorator->SetHighlightActive(true);
280 if(eventData->mGrabHandlePopupEnabled)
282 decorator->SetPopupActive(false);
284 eventData->mDecoratorUpdated = true;
288 case EventData::GRAB_HANDLE_PANNING:
290 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
292 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
293 if(eventData->mCursorBlinkEnabled)
295 decorator->StartCursorBlink();
297 if(eventData->mGrabHandleEnabled)
299 decorator->SetHandleActive(GRAB_HANDLE, true);
301 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
302 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
303 decorator->SetHighlightActive(false);
304 if(eventData->mGrabHandlePopupEnabled)
306 decorator->SetPopupActive(false);
308 eventData->mDecoratorUpdated = true;
312 case EventData::EDITING_WITH_PASTE_POPUP:
314 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
316 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
317 if(eventData->mCursorBlinkEnabled)
319 decorator->StartCursorBlink();
322 if(eventData->mGrabHandleEnabled)
324 decorator->SetHandleActive(GRAB_HANDLE, true);
326 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
327 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
328 decorator->SetHighlightActive(false);
330 if(eventData->mGrabHandlePopupEnabled)
332 impl.SetPopupButtons();
333 decorator->SetPopupActive(true);
335 eventData->mDecoratorUpdated = true;
339 case EventData::TEXT_PANNING:
341 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
342 decorator->StopCursorBlink();
343 decorator->SetHandleActive(GRAB_HANDLE, false);
344 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
345 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
347 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
348 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
349 decorator->SetHighlightActive(true);
352 if(eventData->mGrabHandlePopupEnabled)
354 decorator->SetPopupActive(false);
357 eventData->mDecoratorUpdated = true;
364 } // unnamed Namespace
366 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
367 : mDecorator(decorator),
368 mInputMethodContext(inputMethodContext),
369 mPlaceholderFont(nullptr),
370 mPlaceholderTextActive(),
371 mPlaceholderTextInactive(),
372 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
374 mInputStyleChangedQueue(),
375 mPreviousState(INACTIVE),
377 mPrimaryCursorPosition(0u),
378 mLeftSelectionPosition(0u),
379 mRightSelectionPosition(0u),
380 mPreEditStartPosition(0u),
382 mCursorHookPositionX(0.f),
383 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
384 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
385 mIsShowingPlaceholderText(false),
387 mDecoratorUpdated(false),
388 mCursorBlinkEnabled(true),
389 mGrabHandleEnabled(true),
390 mGrabHandlePopupEnabled(true),
391 mSelectionEnabled(true),
392 mUpdateCursorHookPosition(false),
393 mUpdateCursorPosition(false),
394 mUpdateGrabHandlePosition(false),
395 mUpdateLeftSelectionPosition(false),
396 mUpdateRightSelectionPosition(false),
397 mIsLeftHandleSelected(false),
398 mIsRightHandleSelected(false),
399 mUpdateHighlightBox(false),
400 mScrollAfterUpdatePosition(false),
401 mScrollAfterDelete(false),
402 mAllTextSelected(false),
403 mUpdateInputStyle(false),
404 mPasswordInput(false),
405 mCheckScrollAmount(false),
406 mIsPlaceholderPixelSize(false),
407 mIsPlaceholderElideEnabled(false),
408 mPlaceholderEllipsisFlag(false),
409 mShiftSelectionFlag(true),
410 mUpdateAlignment(false),
411 mEditingEnabled(true)
415 bool Controller::Impl::ProcessInputEvents()
417 return ControllerImplEventHandler::ProcessInputEvents(*this);
420 void Controller::Impl::NotifyInputMethodContext()
422 if(mEventData && mEventData->mInputMethodContext)
424 CharacterIndex cursorPosition = GetLogicalCursorPosition();
426 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
428 // Update the cursor position by removing the initial white spaces.
429 if(cursorPosition < numberOfWhiteSpaces)
435 cursorPosition -= numberOfWhiteSpaces;
438 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
439 mEventData->mInputMethodContext.NotifyCursorPosition();
443 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
445 if(mEventData && mEventData->mInputMethodContext)
447 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
448 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
452 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
454 CharacterIndex cursorPosition = 0u;
458 if((EventData::SELECTING == mEventData->mState) ||
459 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
461 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
465 cursorPosition = mEventData->mPrimaryCursorPosition;
469 return cursorPosition;
472 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
474 Length numberOfWhiteSpaces = 0u;
476 // Get the buffer to the text.
477 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
479 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
480 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
482 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
488 return numberOfWhiteSpaces;
491 void Controller::Impl::GetText(std::string& text) const
493 if(!IsShowingPlaceholderText())
495 // Retrieves the text string.
500 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
504 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
506 // Get the total number of characters.
507 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
509 // Retrieve the text.
510 if(0u != numberOfCharacters)
512 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
516 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
518 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
519 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
521 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
525 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
529 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
531 if(mUpdateTextDirection)
533 // Operations that can be done only once until the text changes.
534 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
542 // Set the update info to relayout the whole text.
543 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
544 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
546 // Make sure the model is up-to-date before layouting
547 UpdateModel(onlyOnceOperations);
550 Relayouter::DoRelayout(*this,
551 Size(MAX_FLOAT, MAX_FLOAT),
552 static_cast<OperationsMask>(onlyOnceOperations |
553 LAYOUT | REORDER | UPDATE_DIRECTION),
554 naturalSize.GetVectorXY());
556 // Do not do again the only once operations.
557 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
559 // Clear the update info. This info will be set the next time the text is updated.
560 mTextUpdateInfo.Clear();
562 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
563 mTextUpdateInfo.mFullRelayoutNeeded = true;
565 mUpdateTextDirection = false;
568 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
571 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
573 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
574 mTextUpdateInfo.mStartGlyphIndex = 0u;
575 mTextUpdateInfo.mStartLineIndex = 0u;
576 numberOfCharacters = 0u;
578 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
579 if(0u == numberOfParagraphs)
581 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
582 numberOfCharacters = 0u;
584 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
586 // Nothing else to do if there are no paragraphs.
590 // Find the paragraphs to be updated.
591 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
592 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
594 // Text is being added at the end of the current text.
595 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
597 // Text is being added in a new paragraph after the last character of the text.
598 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
599 numberOfCharacters = 0u;
600 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
602 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
603 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
605 // Nothing else to do;
609 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
613 Length numberOfCharactersToUpdate = 0u;
614 if(mTextUpdateInfo.mFullRelayoutNeeded)
616 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
620 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
622 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
623 numberOfCharactersToUpdate,
624 paragraphsToBeUpdated);
627 if(0u != paragraphsToBeUpdated.Count())
629 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
630 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
631 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
633 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
634 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
636 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
637 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
638 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
639 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
641 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
642 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
644 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
648 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
652 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
653 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
656 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
658 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
661 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
663 return ControllerImplModelUpdater::Update(*this, operationsRequired);
666 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
668 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
671 float Controller::Impl::GetDefaultFontLineHeight()
673 FontId defaultFontId = 0u;
674 if(nullptr == mFontDefaults)
676 TextAbstraction::FontDescription fontDescription;
677 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale);
681 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale);
684 Text::FontMetrics fontMetrics;
685 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
687 return (fontMetrics.ascender - fontMetrics.descender);
690 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
692 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
694 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
695 mRecalculateNaturalSize = true;
697 RelayoutForNewLineSize();
703 bool Controller::Impl::SetDefaultLineSize(float lineSize)
705 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
707 mLayoutEngine.SetDefaultLineSize(lineSize);
708 mRecalculateNaturalSize = true;
710 RelayoutForNewLineSize();
716 string Controller::Impl::GetSelectedText()
719 if(EventData::SELECTING == mEventData->mState)
721 RetrieveSelection(text, false);
726 string Controller::Impl::CopyText()
729 RetrieveSelection(text, false);
730 SendSelectionToClipboard(false); // Text not modified
732 mEventData->mUpdateCursorPosition = true;
734 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
739 string Controller::Impl::CutText()
742 RetrieveSelection(text, false);
749 SendSelectionToClipboard(true); // Synchronous call to modify text
750 mOperationsPending = ALL_OPERATIONS;
752 if((0u != mModel->mLogicalModel->mText.Count()) ||
753 !IsPlaceholderAvailable())
755 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
759 PlaceholderHandler::ShowPlaceholderText(*this);
762 mEventData->mUpdateCursorPosition = true;
763 mEventData->mScrollAfterDelete = true;
767 if(nullptr != mEditableControlInterface)
769 mEditableControlInterface->TextChanged(true);
774 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
776 if(nullptr == mEventData)
778 // Nothing to do if there is no text.
782 if(mEventData->mSelectionEnabled && (pStart || pEnd))
784 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
785 uint32_t oldStart = mEventData->mLeftSelectionPosition;
786 uint32_t oldEnd = mEventData->mRightSelectionPosition;
790 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
794 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
797 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
799 ChangeState(EventData::EDITING);
800 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
801 mEventData->mUpdateCursorPosition = true;
805 ChangeState(EventData::SELECTING);
806 mEventData->mUpdateHighlightBox = true;
807 mEventData->mUpdateLeftSelectionPosition = true;
808 mEventData->mUpdateRightSelectionPosition = true;
811 if(mSelectableControlInterface != nullptr)
813 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
818 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
820 if(nullptr == mEventData)
824 return mEventData->mPrimaryCursorPosition;
827 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
829 if(nullptr == mEventData)
831 // Nothing to do if there is no text.
835 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
837 // Nothing for same cursor position.
841 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
842 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
843 mEventData->mPrimaryCursorPosition = std::min(index, length);
844 // If there is no focus, only the value is updated.
847 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
848 uint32_t oldStart = mEventData->mLeftSelectionPosition;
849 uint32_t oldEnd = mEventData->mRightSelectionPosition;
850 ChangeState(EventData::EDITING);
851 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
852 mEventData->mUpdateCursorPosition = true;
854 if(mSelectableControlInterface != nullptr && wasInSelectingState)
856 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
859 ScrollTextToMatchCursor();
862 if(nullptr != mEditableControlInterface)
864 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
870 Uint32Pair Controller::Impl::GetTextSelectionRange() const
876 range.first = mEventData->mLeftSelectionPosition;
877 range.second = mEventData->mRightSelectionPosition;
883 bool Controller::Impl::IsEditable() const
885 return mEventData && mEventData->mEditingEnabled;
888 void Controller::Impl::SetEditable(bool editable)
892 mEventData->mEditingEnabled = editable;
894 if(mEventData->mDecorator)
896 mEventData->mDecorator->SetEditable(editable);
901 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
903 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
905 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
907 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
908 mFontDefaults->mFontDescription.family = newDefaultFont;
916 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
918 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
920 // Nothing to select if handles are in the same place.
921 selectedText.clear();
925 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
927 //Get start and end position of selection
928 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
929 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
931 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
932 const Length numberOfCharacters = utf32Characters.Count();
934 // Validate the start and end selection points
935 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
937 //Get text as a UTF8 string
938 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
940 if(deleteAfterRetrieval) // Only delete text if copied successfully
942 // Keep a copy of the current input style.
943 InputStyle currentInputStyle;
944 currentInputStyle.Copy(mEventData->mInputStyle);
946 // Set as input style the style of the first deleted character.
947 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
949 // Compare if the input style has changed.
950 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
952 if(hasInputStyleChanged)
954 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
955 // Queue the input style changed signal.
956 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
959 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
961 // Mark the paragraphs to be updated.
962 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
964 mTextUpdateInfo.mCharacterIndex = 0;
965 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
966 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
967 mTextUpdateInfo.mClearAll = true;
971 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
972 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
975 // Delete text between handles
976 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
977 Vector<Character>::Iterator last = first + lengthOfSelectedText;
978 utf32Characters.Erase(first, last);
980 // Will show the cursor at the first character of the selection.
981 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
985 // Will show the cursor at the last character of the selection.
986 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
989 mEventData->mDecoratorUpdated = true;
993 void Controller::Impl::SetSelection(int start, int end)
995 uint32_t oldStart = mEventData->mLeftSelectionPosition;
996 uint32_t oldEnd = mEventData->mRightSelectionPosition;
998 mEventData->mLeftSelectionPosition = start;
999 mEventData->mRightSelectionPosition = end;
1000 mEventData->mUpdateCursorPosition = true;
1002 if(mSelectableControlInterface != nullptr)
1004 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1008 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1010 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1013 void Controller::Impl::ShowClipboard()
1017 mClipboard.ShowClipboard();
1021 void Controller::Impl::HideClipboard()
1023 if(mClipboard && mClipboardHideEnabled)
1025 mClipboard.HideClipboard();
1029 void Controller::Impl::SetClipboardHideEnable(bool enable)
1031 mClipboardHideEnabled = enable;
1034 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1036 //Send string to clipboard
1037 return (mClipboard && mClipboard.SetItem(source));
1040 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1042 std::string selectedText;
1043 RetrieveSelection(selectedText, deleteAfterSending);
1044 CopyStringToClipboard(selectedText);
1045 ChangeState(EventData::EDITING);
1048 void Controller::Impl::RequestGetTextFromClipboard()
1052 mClipboard.RequestItem();
1056 void Controller::Impl::RepositionSelectionHandles()
1058 SelectionHandleController::Reposition(*this);
1060 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1062 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1065 void Controller::Impl::SetPopupButtons()
1068 * Sets the Popup buttons to be shown depending on State.
1070 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1072 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1075 bool isEditable = IsEditable();
1076 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1078 if(EventData::SELECTING == mEventData->mState)
1080 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1083 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1086 if(!IsClipboardEmpty())
1090 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1092 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1095 if(!mEventData->mAllTextSelected)
1097 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1100 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1102 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1104 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1107 if(!IsClipboardEmpty())
1111 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1113 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1116 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1118 if(!IsClipboardEmpty())
1122 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1124 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1128 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1131 void Controller::Impl::ChangeState(EventData::State newState)
1133 ChangeTextControllerState(*this, newState);
1136 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1137 CursorInfo& cursorInfo)
1139 if(!IsShowingRealText())
1141 // Do not want to use the place-holder text to set the cursor position.
1143 // Use the line's height of the font's family set to set the cursor's size.
1144 // If there is no font's family set, use the default font.
1145 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1147 cursorInfo.lineOffset = 0.f;
1148 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1149 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1152 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1154 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1157 switch(mModel->mHorizontalAlignment)
1159 case Text::HorizontalAlignment::BEGIN:
1163 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1167 cursorInfo.primaryPosition.x = 0.f;
1171 case Text::HorizontalAlignment::CENTER:
1173 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1176 case Text::HorizontalAlignment::END:
1180 cursorInfo.primaryPosition.x = 0.f;
1184 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1190 // Nothing else to do.
1194 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1195 GetCursorPositionParameters parameters;
1196 parameters.visualModel = mModel->mVisualModel;
1197 parameters.logicalModel = mModel->mLogicalModel;
1198 parameters.metrics = mMetrics;
1199 parameters.logical = logical;
1200 parameters.isMultiline = isMultiLine;
1202 Text::GetCursorPosition(parameters,
1205 // Adds Outline offset.
1206 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1207 cursorInfo.primaryPosition.x += outlineWidth;
1208 cursorInfo.primaryPosition.y += outlineWidth;
1209 cursorInfo.secondaryPosition.x += outlineWidth;
1210 cursorInfo.secondaryPosition.y += outlineWidth;
1214 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1216 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1217 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1219 if(0.f > cursorInfo.primaryPosition.x)
1221 cursorInfo.primaryPosition.x = 0.f;
1224 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1225 if(cursorInfo.primaryPosition.x > edgeWidth)
1227 cursorInfo.primaryPosition.x = edgeWidth;
1232 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1234 if(nullptr == mEventData)
1236 // Nothing to do if there is no text input.
1240 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1242 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1243 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1245 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1246 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1248 if(numberOfCharacters > 1u)
1250 const Script script = mModel->mLogicalModel->GetScript(index);
1251 if(HasLigatureMustBreak(script))
1253 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1254 numberOfCharacters = 1u;
1259 while(0u == numberOfCharacters)
1262 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1266 if(index < mEventData->mPrimaryCursorPosition)
1268 cursorIndex -= numberOfCharacters;
1272 cursorIndex += numberOfCharacters;
1275 // Will update the cursor hook position.
1276 mEventData->mUpdateCursorHookPosition = true;
1281 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1283 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1284 if(nullptr == mEventData)
1286 // Nothing to do if there is no text input.
1287 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1291 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1293 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1295 // Sets the cursor position.
1296 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1299 cursorInfo.primaryCursorHeight,
1300 cursorInfo.lineHeight);
1301 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1303 if(mEventData->mUpdateGrabHandlePosition)
1305 // Sets the grab handle position.
1306 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1308 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1309 cursorInfo.lineHeight);
1312 if(cursorInfo.isSecondaryCursor)
1314 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1315 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1316 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1317 cursorInfo.secondaryCursorHeight,
1318 cursorInfo.lineHeight);
1319 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1322 // Set which cursors are active according the state.
1323 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1325 if(cursorInfo.isSecondaryCursor)
1327 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1331 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1336 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1339 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1342 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1343 const CursorInfo& cursorInfo)
1345 SelectionHandleController::Update(*this, handleType, cursorInfo);
1348 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1350 // Clamp between -space & -alignment offset.
1352 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1354 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1355 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1356 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1358 mEventData->mDecoratorUpdated = true;
1362 mModel->mScrollPosition.x = 0.f;
1366 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1368 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1370 // Nothing to do if the text is single line.
1374 // Clamp between -space & 0.
1375 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1377 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1378 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1379 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1381 mEventData->mDecoratorUpdated = true;
1385 mModel->mScrollPosition.y = 0.f;
1389 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1391 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1393 // position is in actor's coords.
1394 const float positionEndX = position.x + cursorWidth;
1395 const float positionEndY = position.y + lineHeight;
1397 // Transform the position to decorator coords.
1398 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1399 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1401 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1402 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1404 if(decoratorPositionBeginX < 0.f)
1406 mModel->mScrollPosition.x = -position.x;
1408 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1410 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1413 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1415 if(decoratorPositionBeginY < 0.f)
1417 mModel->mScrollPosition.y = -position.y;
1419 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1421 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1426 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1428 // Get the current cursor position in decorator coords.
1429 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1431 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1433 // Calculate the offset to match the cursor position before the character was deleted.
1434 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1436 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1437 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1439 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1440 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1443 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1444 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1446 // Makes the new cursor position visible if needed.
1447 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1450 void Controller::Impl::ScrollTextToMatchCursor()
1452 CursorInfo cursorInfo;
1453 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1454 ScrollTextToMatchCursor(cursorInfo);
1457 void Controller::Impl::RequestRelayout()
1459 if(nullptr != mControlInterface)
1461 mControlInterface->RequestTextRelayout();
1465 void Controller::Impl::RelayoutForNewLineSize()
1467 // relayout all characters
1468 mTextUpdateInfo.mCharacterIndex = 0;
1469 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1470 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1471 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1474 if(mEventData && mEventData->mState == EventData::SELECTING)
1476 ChangeState(EventData::EDITING);
1482 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1484 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1487 void Controller::Impl::ProcessInputStyleChangedSignals()
1491 if(mEditableControlInterface)
1493 // Emit the input style changed signal for each mask
1494 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1495 mEventData->mInputStyleChangedQueue.end(),
1496 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); } );
1499 mEventData->mInputStyleChangedQueue.Clear();
1503 void Controller::Impl::ScrollBy(Vector2 scroll)
1505 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1507 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1508 const Vector2 currentScroll = mModel->mScrollPosition;
1510 scroll.x = -scroll.x;
1511 scroll.y = -scroll.y;
1513 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1515 mModel->mScrollPosition.x += scroll.x;
1516 ClampHorizontalScroll(layoutSize);
1519 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1521 mModel->mScrollPosition.y += scroll.y;
1522 ClampVerticalScroll(layoutSize);
1525 if(mModel->mScrollPosition != currentScroll)
1527 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1533 float Controller::Impl::GetHorizontalScrollPosition()
1535 // Scroll values are negative internally so we convert them to positive numbers
1536 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1539 float Controller::Impl::GetVerticalScrollPosition()
1541 // Scroll values are negative internally so we convert them to positive numbers
1542 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1545 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1548 return Vector3(10.f, 10.f, 10.f);
1551 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1554 return Vector2(10.f, 10.f);
1557 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1559 auto actor = Toolkit::TextAnchor::New();
1560 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1561 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1562 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1563 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1564 const Vector2 anchorSize = GetAnchorSize(anchor);
1565 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1566 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1567 actor.SetProperty(Actor::Property::NAME, anchorText);
1568 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1569 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1570 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1574 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1576 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1577 we need to create and destroy potentially many actors. Some optimization can be considered here.
1578 Maybe a "dirty" flag in mLogicalModel? */
1579 anchorActors.clear();
1580 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1582 auto actor = CreateAnchorActor(anchor);
1583 anchorActors.push_back(actor);
1587 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1589 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1591 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1596 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1599 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1601 //Underlined character runs for markup-processor
1602 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1603 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1604 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1606 if(shouldClearPreUnderlineRuns)
1608 mModel->mVisualModel->mUnderlineRuns.Clear();
1611 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1613 CharacterIndex characterIndex = it->characterRun.characterIndex;
1614 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1615 for(Length index = 0u; index < numberOfCharacters; index++)
1617 GlyphRun underlineGlyphRun;
1618 underlineGlyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1619 underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1620 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1625 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1627 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1629 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1632 UPDATE_LAYOUT_SIZE |
1637 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1638 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1642 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1645 mIsAutoScrollEnabled = enable;
1650 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1651 mIsAutoScrollEnabled = false;
1655 void Controller::Impl::SetEnableCursorBlink(bool enable)
1657 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1661 mEventData->mCursorBlinkEnabled = enable;
1663 if(!enable && mEventData->mDecorator)
1665 mEventData->mDecorator->StopCursorBlink();
1670 void Controller::Impl::SetMultiLineEnabled(bool enable)
1672 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1674 if(layout != mLayoutEngine.GetLayout())
1676 // Set the layout type.
1677 mLayoutEngine.SetLayout(layout);
1679 // Set the flags to redo the layout operations
1680 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1681 UPDATE_LAYOUT_SIZE |
1685 mTextUpdateInfo.mFullRelayoutNeeded = true;
1686 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1688 // Need to recalculate natural size
1689 mRecalculateNaturalSize = true;
1695 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1697 if(alignment != mModel->mHorizontalAlignment)
1699 // Set the alignment.
1700 mModel->mHorizontalAlignment = alignment;
1702 // Set the flag to redo the alignment operation.
1703 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1707 mEventData->mUpdateAlignment = true;
1709 // Update the cursor if it's in editing mode
1710 if(EventData::IsEditingState(mEventData->mState))
1712 ChangeState(EventData::EDITING);
1713 mEventData->mUpdateCursorPosition = true;
1721 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1723 if(alignment != mModel->mVerticalAlignment)
1725 // Set the alignment.
1726 mModel->mVerticalAlignment = alignment;
1727 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1732 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1734 if(lineWrapMode != mModel->mLineWrapMode)
1736 // Update Text layout for applying wrap mode
1737 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1740 UPDATE_LAYOUT_SIZE |
1743 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1744 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1746 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1749 // Set the text wrap mode.
1750 mModel->mLineWrapMode = lineWrapMode;
1752 mTextUpdateInfo.mCharacterIndex = 0u;
1753 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1754 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1761 void Controller::Impl::SetDefaultColor(const Vector4& color)
1765 if(!IsShowingPlaceholderText())
1767 mModel->mVisualModel->SetTextColor(color);
1768 mModel->mLogicalModel->mColorRuns.Clear();
1769 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1774 void Controller::Impl::ClearFontData()
1778 mFontDefaults->mFontId = 0u; // Remove old font ID
1781 // Set flags to update the model.
1782 mTextUpdateInfo.mCharacterIndex = 0u;
1783 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1784 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1786 mTextUpdateInfo.mClearAll = true;
1787 mTextUpdateInfo.mFullRelayoutNeeded = true;
1788 mRecalculateNaturalSize = true;
1790 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1796 UPDATE_LAYOUT_SIZE |
1801 void Controller::Impl::ClearStyleData()
1803 mModel->mLogicalModel->mColorRuns.Clear();
1804 mModel->mLogicalModel->ClearFontDescriptionRuns();
1808 void Controller::Impl::ResetScrollPosition()
1812 // Reset the scroll position.
1813 mModel->mScrollPosition = Vector2::ZERO;
1814 mEventData->mScrollAfterUpdatePosition = true;
1818 } // namespace Dali::Toolkit::Text