2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/devel-api/adaptor-framework/window-devel.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/actors/layer.h>
25 #include <dali/public-api/rendering/renderer.h>
29 #include <dali-toolkit/internal/text/character-set-conversion.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
32 #include <dali-toolkit/internal/text/text-control-interface.h>
33 #include <dali-toolkit/internal/text/text-controller-impl-data-clearer.h>
34 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
35 #include <dali-toolkit/internal/text/text-controller-impl-model-updater.h>
36 #include <dali-toolkit/internal/text/text-controller-placeholder-handler.h>
37 #include <dali-toolkit/internal/text/text-controller-relayouter.h>
38 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
39 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
40 #include <dali-toolkit/internal/text/text-run-container.h>
41 #include <dali-toolkit/internal/text/text-selection-handle-controller.h>
47 #if defined(DEBUG_ENABLED)
48 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
51 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
53 const std::string EMPTY_STRING("");
57 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 * GetFontSizeScale());
681 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
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);
696 RelayoutAllCharacters();
702 bool Controller::Impl::SetDefaultLineSize(float lineSize)
704 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
706 mLayoutEngine.SetDefaultLineSize(lineSize);
708 RelayoutAllCharacters();
714 string Controller::Impl::GetSelectedText()
717 if(EventData::SELECTING == mEventData->mState)
719 RetrieveSelection(text, false);
724 string Controller::Impl::CopyText()
727 RetrieveSelection(text, false);
728 SendSelectionToClipboard(false); // Text not modified
730 mEventData->mUpdateCursorPosition = true;
732 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
737 string Controller::Impl::CutText()
740 RetrieveSelection(text, false);
747 SendSelectionToClipboard(true); // Synchronous call to modify text
748 mOperationsPending = ALL_OPERATIONS;
750 if((0u != mModel->mLogicalModel->mText.Count()) ||
751 !IsPlaceholderAvailable())
753 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
757 PlaceholderHandler::ShowPlaceholderText(*this);
760 mEventData->mUpdateCursorPosition = true;
761 mEventData->mScrollAfterDelete = true;
765 if(nullptr != mEditableControlInterface)
767 mEditableControlInterface->TextChanged(true);
772 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
774 if(nullptr == mEventData)
776 // Nothing to do if there is no text.
780 if(mEventData->mSelectionEnabled && (pStart || pEnd))
782 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
783 uint32_t oldStart = mEventData->mLeftSelectionPosition;
784 uint32_t oldEnd = mEventData->mRightSelectionPosition;
788 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
792 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
795 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
797 ChangeState(EventData::EDITING);
798 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
799 mEventData->mUpdateCursorPosition = true;
803 ChangeState(EventData::SELECTING);
804 mEventData->mUpdateHighlightBox = true;
805 mEventData->mUpdateLeftSelectionPosition = true;
806 mEventData->mUpdateRightSelectionPosition = true;
809 if(mSelectableControlInterface != nullptr)
811 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
816 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
818 if(nullptr == mEventData)
822 return mEventData->mPrimaryCursorPosition;
825 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
827 if(nullptr == mEventData)
829 // Nothing to do if there is no text.
833 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
835 // Nothing for same cursor position.
839 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
840 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
841 mEventData->mPrimaryCursorPosition = std::min(index, length);
842 // If there is no focus, only the value is updated.
845 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
846 uint32_t oldStart = mEventData->mLeftSelectionPosition;
847 uint32_t oldEnd = mEventData->mRightSelectionPosition;
848 ChangeState(EventData::EDITING);
849 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
850 mEventData->mUpdateCursorPosition = true;
852 if(mSelectableControlInterface != nullptr && wasInSelectingState)
854 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
857 ScrollTextToMatchCursor();
860 if(nullptr != mEditableControlInterface)
862 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
868 Uint32Pair Controller::Impl::GetTextSelectionRange() const
874 range.first = mEventData->mLeftSelectionPosition;
875 range.second = mEventData->mRightSelectionPosition;
881 bool Controller::Impl::IsEditable() const
883 return mEventData && mEventData->mEditingEnabled;
886 void Controller::Impl::SetEditable(bool editable)
890 mEventData->mEditingEnabled = editable;
892 if(mEventData->mDecorator)
894 mEventData->mDecorator->SetEditable(editable);
899 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
901 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
903 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
905 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
906 mFontDefaults->mFontDescription.family = newDefaultFont;
914 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
916 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
918 // Nothing to select if handles are in the same place.
919 selectedText.clear();
923 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
925 //Get start and end position of selection
926 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
927 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
929 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
930 const Length numberOfCharacters = utf32Characters.Count();
932 // Validate the start and end selection points
933 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
935 //Get text as a UTF8 string
936 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
938 if(deleteAfterRetrieval) // Only delete text if copied successfully
940 // Keep a copy of the current input style.
941 InputStyle currentInputStyle;
942 currentInputStyle.Copy(mEventData->mInputStyle);
944 // Set as input style the style of the first deleted character.
945 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
947 // Compare if the input style has changed.
948 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
950 if(hasInputStyleChanged)
952 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
953 // Queue the input style changed signal.
954 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
957 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
959 // Mark the paragraphs to be updated.
960 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
962 mTextUpdateInfo.mCharacterIndex = 0;
963 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
964 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
965 mTextUpdateInfo.mClearAll = true;
969 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
970 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
973 // Delete text between handles
974 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
975 Vector<Character>::Iterator last = first + lengthOfSelectedText;
976 utf32Characters.Erase(first, last);
978 // Will show the cursor at the first character of the selection.
979 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
983 // Will show the cursor at the last character of the selection.
984 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
987 mEventData->mDecoratorUpdated = true;
991 void Controller::Impl::SetSelection(int start, int end)
993 uint32_t oldStart = mEventData->mLeftSelectionPosition;
994 uint32_t oldEnd = mEventData->mRightSelectionPosition;
996 mEventData->mLeftSelectionPosition = start;
997 mEventData->mRightSelectionPosition = end;
998 mEventData->mUpdateCursorPosition = true;
1000 if(mSelectableControlInterface != nullptr)
1002 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1006 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1008 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1011 void Controller::Impl::ShowClipboard()
1015 mClipboard.ShowClipboard();
1019 void Controller::Impl::HideClipboard()
1021 if(mClipboard && mClipboardHideEnabled)
1023 mClipboard.HideClipboard();
1027 void Controller::Impl::SetClipboardHideEnable(bool enable)
1029 mClipboardHideEnabled = enable;
1032 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1034 //Send string to clipboard
1035 return (mClipboard && mClipboard.SetItem(source));
1038 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1040 std::string selectedText;
1041 RetrieveSelection(selectedText, deleteAfterSending);
1042 CopyStringToClipboard(selectedText);
1043 ChangeState(EventData::EDITING);
1046 void Controller::Impl::RequestGetTextFromClipboard()
1050 mClipboard.RequestItem();
1054 void Controller::Impl::RepositionSelectionHandles()
1056 SelectionHandleController::Reposition(*this);
1058 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1060 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1063 void Controller::Impl::SetPopupButtons()
1066 * Sets the Popup buttons to be shown depending on State.
1068 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1070 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1073 bool isEditable = IsEditable();
1074 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1076 if(EventData::SELECTING == mEventData->mState)
1078 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1081 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1084 if(!IsClipboardEmpty())
1088 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1090 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1093 if(!mEventData->mAllTextSelected)
1095 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1098 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1100 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1102 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1105 if(!IsClipboardEmpty())
1109 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1111 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1114 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1116 if(!IsClipboardEmpty())
1120 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1122 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1126 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1129 void Controller::Impl::ChangeState(EventData::State newState)
1131 ChangeTextControllerState(*this, newState);
1134 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1135 CursorInfo& cursorInfo)
1137 if(!IsShowingRealText())
1139 // Do not want to use the place-holder text to set the cursor position.
1141 // Use the line's height of the font's family set to set the cursor's size.
1142 // If there is no font's family set, use the default font.
1143 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1145 cursorInfo.lineOffset = 0.f;
1146 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1147 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1150 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1152 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1155 switch(mModel->mHorizontalAlignment)
1157 case Text::HorizontalAlignment::BEGIN:
1161 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1165 cursorInfo.primaryPosition.x = 0.f;
1169 case Text::HorizontalAlignment::CENTER:
1171 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1174 case Text::HorizontalAlignment::END:
1178 cursorInfo.primaryPosition.x = 0.f;
1182 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1188 // Nothing else to do.
1192 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1193 GetCursorPositionParameters parameters;
1194 parameters.visualModel = mModel->mVisualModel;
1195 parameters.logicalModel = mModel->mLogicalModel;
1196 parameters.metrics = mMetrics;
1197 parameters.logical = logical;
1198 parameters.isMultiline = isMultiLine;
1200 float defaultFontLineHeight = GetDefaultFontLineHeight();
1202 Text::GetCursorPosition(parameters,
1203 defaultFontLineHeight,
1206 // Adds Outline offset.
1207 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1208 cursorInfo.primaryPosition.x += outlineWidth;
1209 cursorInfo.primaryPosition.y += outlineWidth;
1210 cursorInfo.secondaryPosition.x += outlineWidth;
1211 cursorInfo.secondaryPosition.y += outlineWidth;
1215 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1217 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1218 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1220 if(0.f > cursorInfo.primaryPosition.x)
1222 cursorInfo.primaryPosition.x = 0.f;
1225 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1226 if(cursorInfo.primaryPosition.x > edgeWidth)
1228 cursorInfo.primaryPosition.x = edgeWidth;
1233 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1235 if(nullptr == mEventData)
1237 // Nothing to do if there is no text input.
1241 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1243 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1244 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1246 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1247 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1249 if(numberOfCharacters > 1u)
1251 const Script script = mModel->mLogicalModel->GetScript(index);
1252 if(HasLigatureMustBreak(script))
1254 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1255 numberOfCharacters = 1u;
1260 while(0u == numberOfCharacters)
1263 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1267 if(index < mEventData->mPrimaryCursorPosition)
1269 cursorIndex -= numberOfCharacters;
1273 cursorIndex += numberOfCharacters;
1276 // Will update the cursor hook position.
1277 mEventData->mUpdateCursorHookPosition = true;
1282 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1284 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1285 if(nullptr == mEventData)
1287 // Nothing to do if there is no text input.
1288 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1292 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1294 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1296 // Sets the cursor position.
1297 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1300 cursorInfo.primaryCursorHeight,
1301 cursorInfo.lineHeight);
1302 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1304 if(mEventData->mUpdateGrabHandlePosition)
1306 // Sets the grab handle position.
1307 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1309 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1310 cursorInfo.lineHeight);
1313 if(cursorInfo.isSecondaryCursor)
1315 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1316 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1317 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1318 cursorInfo.secondaryCursorHeight,
1319 cursorInfo.lineHeight);
1320 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1323 // Set which cursors are active according the state.
1324 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1326 if(cursorInfo.isSecondaryCursor)
1328 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1332 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1337 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1340 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1343 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1344 const CursorInfo& cursorInfo)
1346 SelectionHandleController::Update(*this, handleType, cursorInfo);
1349 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1351 // Clamp between -space & -alignment offset.
1353 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1355 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1356 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1357 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1359 mEventData->mDecoratorUpdated = true;
1363 mModel->mScrollPosition.x = 0.f;
1367 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1369 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1371 // Nothing to do if the text is single line.
1375 // Clamp between -space & 0.
1376 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1378 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1379 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1380 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1382 mEventData->mDecoratorUpdated = true;
1386 mModel->mScrollPosition.y = 0.f;
1390 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1392 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1394 // position is in actor's coords.
1395 const float positionEndX = position.x + cursorWidth;
1396 const float positionEndY = position.y + lineHeight;
1398 // Transform the position to decorator coords.
1399 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1400 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1402 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1403 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1405 if(decoratorPositionBeginX < 0.f)
1407 mModel->mScrollPosition.x = -position.x;
1409 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1411 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1414 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1416 if(decoratorPositionBeginY < 0.f)
1418 mModel->mScrollPosition.y = -position.y;
1420 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1422 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1427 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1429 // Get the current cursor position in decorator coords.
1430 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1432 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1434 // Calculate the offset to match the cursor position before the character was deleted.
1435 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1437 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1438 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1440 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1441 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1444 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1445 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1447 // Makes the new cursor position visible if needed.
1448 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1451 void Controller::Impl::ScrollTextToMatchCursor()
1453 CursorInfo cursorInfo;
1454 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1455 ScrollTextToMatchCursor(cursorInfo);
1458 void Controller::Impl::RequestRelayout()
1460 if(nullptr != mControlInterface)
1462 mControlInterface->RequestTextRelayout();
1466 void Controller::Impl::RelayoutAllCharacters()
1468 // relayout all characters
1469 mTextUpdateInfo.mCharacterIndex = 0;
1470 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1471 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1472 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1474 mTextUpdateInfo.mFullRelayoutNeeded = true;
1476 // Need to recalculate natural size
1477 mRecalculateNaturalSize = true;
1480 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1482 ChangeState(EventData::EDITING);
1488 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1490 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1493 void Controller::Impl::ProcessInputStyleChangedSignals()
1497 if(mEditableControlInterface)
1499 // Emit the input style changed signal for each mask
1500 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1501 mEventData->mInputStyleChangedQueue.end(),
1502 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1505 mEventData->mInputStyleChangedQueue.Clear();
1509 void Controller::Impl::ScrollBy(Vector2 scroll)
1511 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1513 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1514 const Vector2 currentScroll = mModel->mScrollPosition;
1516 scroll.x = -scroll.x;
1517 scroll.y = -scroll.y;
1519 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1521 mModel->mScrollPosition.x += scroll.x;
1522 ClampHorizontalScroll(layoutSize);
1525 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1527 mModel->mScrollPosition.y += scroll.y;
1528 ClampVerticalScroll(layoutSize);
1531 if(mModel->mScrollPosition != currentScroll)
1533 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1539 float Controller::Impl::GetHorizontalScrollPosition()
1541 // Scroll values are negative internally so we convert them to positive numbers
1542 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1545 float Controller::Impl::GetVerticalScrollPosition()
1547 // Scroll values are negative internally so we convert them to positive numbers
1548 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1551 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1554 return Vector3(10.f, 10.f, 10.f);
1557 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1560 return Vector2(10.f, 10.f);
1563 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1565 auto actor = Toolkit::TextAnchor::New();
1566 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1567 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1568 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1569 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1570 const Vector2 anchorSize = GetAnchorSize(anchor);
1571 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1572 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1573 actor.SetProperty(Actor::Property::NAME, anchorText);
1574 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1575 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1576 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1580 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1582 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1583 we need to create and destroy potentially many actors. Some optimization can be considered here.
1584 Maybe a "dirty" flag in mLogicalModel? */
1585 anchorActors.clear();
1586 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1588 auto actor = CreateAnchorActor(anchor);
1589 anchorActors.push_back(actor);
1593 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1595 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1597 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1602 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1605 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1607 //Underlined character runs for markup-processor
1608 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1609 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1610 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1612 if(shouldClearPreUnderlineRuns)
1614 mModel->mVisualModel->mUnderlineRuns.Clear();
1617 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1619 CharacterIndex characterIndex = it->characterRun.characterIndex;
1620 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1621 for(Length index = 0u; index < numberOfCharacters; index++)
1623 GlyphRun underlineGlyphRun;
1624 underlineGlyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1625 underlineGlyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1626 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1631 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1633 //Strikethrough character runs from markup-processor
1634 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1635 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1636 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1638 mModel->mVisualModel->mStrikethroughRuns.Clear();
1640 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1642 CharacterIndex characterIndex = it->characterRun.characterIndex;
1643 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1644 StrikethroughGlyphRun strikethroughGlyphRun;
1645 strikethroughGlyphRun.color = it->color;
1646 strikethroughGlyphRun.isColorSet = it->isColorSet;
1647 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1648 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1650 for(Length index = 1u; index < numberOfCharacters; index++)
1652 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1655 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1659 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1661 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1663 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1666 UPDATE_LAYOUT_SIZE |
1671 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1672 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1676 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1679 mIsAutoScrollEnabled = enable;
1684 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1685 mIsAutoScrollEnabled = false;
1689 void Controller::Impl::SetEnableCursorBlink(bool enable)
1691 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1695 mEventData->mCursorBlinkEnabled = enable;
1697 if(!enable && mEventData->mDecorator)
1699 mEventData->mDecorator->StopCursorBlink();
1704 void Controller::Impl::SetMultiLineEnabled(bool enable)
1706 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1708 if(layout != mLayoutEngine.GetLayout())
1710 // Set the layout type.
1711 mLayoutEngine.SetLayout(layout);
1713 // Set the flags to redo the layout operations
1714 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1715 UPDATE_LAYOUT_SIZE |
1719 mTextUpdateInfo.mFullRelayoutNeeded = true;
1720 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1722 // Need to recalculate natural size
1723 mRecalculateNaturalSize = true;
1729 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1731 if(alignment != mModel->mHorizontalAlignment)
1733 // Set the alignment.
1734 mModel->mHorizontalAlignment = alignment;
1736 // Set the flag to redo the alignment operation.
1737 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1741 mEventData->mUpdateAlignment = true;
1743 // Update the cursor if it's in editing mode
1744 if(EventData::IsEditingState(mEventData->mState))
1746 ChangeState(EventData::EDITING);
1747 mEventData->mUpdateCursorPosition = true;
1755 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1757 if(alignment != mModel->mVerticalAlignment)
1759 // Set the alignment.
1760 mModel->mVerticalAlignment = alignment;
1761 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1766 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1768 if(lineWrapMode != mModel->mLineWrapMode)
1770 // Update Text layout for applying wrap mode
1771 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1774 UPDATE_LAYOUT_SIZE |
1777 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1778 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1780 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1783 // Set the text wrap mode.
1784 mModel->mLineWrapMode = lineWrapMode;
1786 mTextUpdateInfo.mCharacterIndex = 0u;
1787 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1788 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1795 void Controller::Impl::SetDefaultColor(const Vector4& color)
1799 if(!IsShowingPlaceholderText())
1801 mModel->mVisualModel->SetTextColor(color);
1802 mModel->mLogicalModel->mColorRuns.Clear();
1803 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1808 void Controller::Impl::ClearFontData()
1812 mFontDefaults->mFontId = 0u; // Remove old font ID
1815 // Set flags to update the model.
1816 mTextUpdateInfo.mCharacterIndex = 0u;
1817 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1818 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1820 mTextUpdateInfo.mClearAll = true;
1821 mTextUpdateInfo.mFullRelayoutNeeded = true;
1822 mRecalculateNaturalSize = true;
1824 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1830 UPDATE_LAYOUT_SIZE |
1835 void Controller::Impl::ClearStyleData()
1837 mModel->mLogicalModel->mColorRuns.Clear();
1838 mModel->mLogicalModel->ClearFontDescriptionRuns();
1841 void Controller::Impl::ResetScrollPosition()
1845 // Reset the scroll position.
1846 mModel->mScrollPosition = Vector2::ZERO;
1847 mEventData->mScrollAfterUpdatePosition = true;
1851 } // namespace Dali::Toolkit::Text