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>
42 #include <dali-toolkit/internal/text/underlined-glyph-run.h>
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
52 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
54 const std::string EMPTY_STRING("");
58 namespace Dali::Toolkit::Text
62 void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const fontDefaults, const Vector4& textColor)
64 // Sets the default text's color.
65 inputStyle.textColor = textColor;
66 inputStyle.isDefaultColor = true;
68 inputStyle.familyName.clear();
69 inputStyle.weight = TextAbstraction::FontWeight::NORMAL;
70 inputStyle.width = TextAbstraction::FontWidth::NORMAL;
71 inputStyle.slant = TextAbstraction::FontSlant::NORMAL;
72 inputStyle.size = 0.f;
74 inputStyle.lineSpacing = 0.f;
76 inputStyle.underlineProperties.clear();
77 inputStyle.shadowProperties.clear();
78 inputStyle.embossProperties.clear();
79 inputStyle.outlineProperties.clear();
81 inputStyle.isFamilyDefined = false;
82 inputStyle.isWeightDefined = false;
83 inputStyle.isWidthDefined = false;
84 inputStyle.isSlantDefined = false;
85 inputStyle.isSizeDefined = false;
87 inputStyle.isLineSpacingDefined = false;
89 inputStyle.isUnderlineDefined = false;
90 inputStyle.isShadowDefined = false;
91 inputStyle.isEmbossDefined = false;
92 inputStyle.isOutlineDefined = false;
94 // Sets the default font's family name, weight, width, slant and size.
97 if(fontDefaults->familyDefined)
99 inputStyle.familyName = fontDefaults->mFontDescription.family;
100 inputStyle.isFamilyDefined = true;
103 if(fontDefaults->weightDefined)
105 inputStyle.weight = fontDefaults->mFontDescription.weight;
106 inputStyle.isWeightDefined = true;
109 if(fontDefaults->widthDefined)
111 inputStyle.width = fontDefaults->mFontDescription.width;
112 inputStyle.isWidthDefined = true;
115 if(fontDefaults->slantDefined)
117 inputStyle.slant = fontDefaults->mFontDescription.slant;
118 inputStyle.isSlantDefined = true;
121 if(fontDefaults->sizeDefined)
123 inputStyle.size = fontDefaults->mDefaultPointSize;
124 inputStyle.isSizeDefined = true;
129 void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState)
131 EventData* eventData = impl.mEventData;
133 if(nullptr == eventData)
135 // Nothing to do if there is no text input.
139 DecoratorPtr& decorator = eventData->mDecorator;
142 // Nothing to do if there is no decorator.
146 DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState);
148 if(eventData->mState != newState)
150 eventData->mPreviousState = eventData->mState;
151 eventData->mState = newState;
153 switch(eventData->mState)
155 case EventData::INACTIVE:
157 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
158 decorator->StopCursorBlink();
159 decorator->SetHandleActive(GRAB_HANDLE, false);
160 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
161 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
162 decorator->SetHighlightActive(false);
163 decorator->SetPopupActive(false);
164 eventData->mDecoratorUpdated = true;
168 case EventData::INTERRUPTED:
170 decorator->SetHandleActive(GRAB_HANDLE, false);
171 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
172 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
173 decorator->SetHighlightActive(false);
174 decorator->SetPopupActive(false);
175 eventData->mDecoratorUpdated = true;
179 case EventData::SELECTING:
181 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
182 decorator->StopCursorBlink();
183 decorator->SetHandleActive(GRAB_HANDLE, false);
184 if(eventData->mGrabHandleEnabled)
186 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
187 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
189 decorator->SetHighlightActive(true);
190 if(eventData->mGrabHandlePopupEnabled)
192 impl.SetPopupButtons();
193 decorator->SetPopupActive(true);
195 eventData->mDecoratorUpdated = true;
199 case EventData::EDITING:
201 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
202 if(eventData->mCursorBlinkEnabled)
204 decorator->StartCursorBlink();
206 // Grab handle is not shown until a tap is received whilst EDITING
207 decorator->SetHandleActive(GRAB_HANDLE, false);
208 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
209 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
210 decorator->SetHighlightActive(false);
211 if(eventData->mGrabHandlePopupEnabled)
213 decorator->SetPopupActive(false);
215 eventData->mDecoratorUpdated = true;
218 case EventData::EDITING_WITH_POPUP:
220 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState);
222 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
223 if(eventData->mCursorBlinkEnabled)
225 decorator->StartCursorBlink();
227 if(eventData->mSelectionEnabled)
229 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
230 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
231 decorator->SetHighlightActive(false);
233 else if(eventData->mGrabHandleEnabled)
235 decorator->SetHandleActive(GRAB_HANDLE, true);
237 if(eventData->mGrabHandlePopupEnabled)
239 impl.SetPopupButtons();
240 decorator->SetPopupActive(true);
242 eventData->mDecoratorUpdated = true;
245 case EventData::EDITING_WITH_GRAB_HANDLE:
247 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState);
249 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
250 if(eventData->mCursorBlinkEnabled)
252 decorator->StartCursorBlink();
254 // Grab handle is not shown until a tap is received whilst EDITING
255 if(eventData->mGrabHandleEnabled)
257 decorator->SetHandleActive(GRAB_HANDLE, true);
259 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
260 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
261 decorator->SetHighlightActive(false);
262 if(eventData->mGrabHandlePopupEnabled)
264 decorator->SetPopupActive(false);
266 eventData->mDecoratorUpdated = true;
270 case EventData::SELECTION_HANDLE_PANNING:
272 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
273 decorator->StopCursorBlink();
274 decorator->SetHandleActive(GRAB_HANDLE, false);
275 if(eventData->mGrabHandleEnabled)
277 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true);
278 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true);
280 decorator->SetHighlightActive(true);
281 if(eventData->mGrabHandlePopupEnabled)
283 decorator->SetPopupActive(false);
285 eventData->mDecoratorUpdated = true;
289 case EventData::GRAB_HANDLE_PANNING:
291 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState);
293 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
294 if(eventData->mCursorBlinkEnabled)
296 decorator->StartCursorBlink();
298 if(eventData->mGrabHandleEnabled)
300 decorator->SetHandleActive(GRAB_HANDLE, true);
302 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
303 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
304 decorator->SetHighlightActive(false);
305 if(eventData->mGrabHandlePopupEnabled)
307 decorator->SetPopupActive(false);
309 eventData->mDecoratorUpdated = true;
313 case EventData::EDITING_WITH_PASTE_POPUP:
315 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState);
317 decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
318 if(eventData->mCursorBlinkEnabled)
320 decorator->StartCursorBlink();
323 if(eventData->mGrabHandleEnabled)
325 decorator->SetHandleActive(GRAB_HANDLE, true);
327 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
328 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
329 decorator->SetHighlightActive(false);
331 if(eventData->mGrabHandlePopupEnabled)
333 impl.SetPopupButtons();
334 decorator->SetPopupActive(true);
336 eventData->mDecoratorUpdated = true;
340 case EventData::TEXT_PANNING:
342 decorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
343 decorator->StopCursorBlink();
344 decorator->SetHandleActive(GRAB_HANDLE, false);
345 if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) ||
346 decorator->IsHandleActive(RIGHT_SELECTION_HANDLE))
348 decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false);
349 decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false);
350 decorator->SetHighlightActive(true);
353 if(eventData->mGrabHandlePopupEnabled)
355 decorator->SetPopupActive(false);
358 eventData->mDecoratorUpdated = true;
365 } // unnamed Namespace
367 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
368 : mDecorator(decorator),
369 mInputMethodContext(inputMethodContext),
370 mPlaceholderFont(nullptr),
371 mPlaceholderTextActive(),
372 mPlaceholderTextInactive(),
373 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
375 mInputStyleChangedQueue(),
376 mPreviousState(INACTIVE),
378 mPrimaryCursorPosition(0u),
379 mLeftSelectionPosition(0u),
380 mRightSelectionPosition(0u),
381 mPreEditStartPosition(0u),
383 mCursorHookPositionX(0.f),
384 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
385 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
386 mIsShowingPlaceholderText(false),
388 mDecoratorUpdated(false),
389 mCursorBlinkEnabled(true),
390 mGrabHandleEnabled(true),
391 mGrabHandlePopupEnabled(true),
392 mSelectionEnabled(true),
393 mUpdateCursorHookPosition(false),
394 mUpdateCursorPosition(false),
395 mUpdateGrabHandlePosition(false),
396 mUpdateLeftSelectionPosition(false),
397 mUpdateRightSelectionPosition(false),
398 mIsLeftHandleSelected(false),
399 mIsRightHandleSelected(false),
400 mUpdateHighlightBox(false),
401 mScrollAfterUpdatePosition(false),
402 mScrollAfterDelete(false),
403 mAllTextSelected(false),
404 mUpdateInputStyle(false),
405 mPasswordInput(false),
406 mCheckScrollAmount(false),
407 mIsPlaceholderPixelSize(false),
408 mIsPlaceholderElideEnabled(false),
409 mPlaceholderEllipsisFlag(false),
410 mShiftSelectionFlag(true),
411 mUpdateAlignment(false),
412 mEditingEnabled(true)
416 bool Controller::Impl::ProcessInputEvents()
418 return ControllerImplEventHandler::ProcessInputEvents(*this);
421 void Controller::Impl::NotifyInputMethodContext()
423 if(mEventData && mEventData->mInputMethodContext)
425 CharacterIndex cursorPosition = GetLogicalCursorPosition();
427 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
429 // Update the cursor position by removing the initial white spaces.
430 if(cursorPosition < numberOfWhiteSpaces)
436 cursorPosition -= numberOfWhiteSpaces;
439 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
440 mEventData->mInputMethodContext.NotifyCursorPosition();
444 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
446 if(mEventData && mEventData->mInputMethodContext)
448 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
449 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
453 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
455 CharacterIndex cursorPosition = 0u;
459 if((EventData::SELECTING == mEventData->mState) ||
460 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
462 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
466 cursorPosition = mEventData->mPrimaryCursorPosition;
470 return cursorPosition;
473 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
475 Length numberOfWhiteSpaces = 0u;
477 // Get the buffer to the text.
478 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
480 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
481 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
483 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
489 return numberOfWhiteSpaces;
492 void Controller::Impl::GetText(std::string& text) const
494 if(!IsShowingPlaceholderText())
496 // Retrieves the text string.
501 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
505 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
507 // Get the total number of characters.
508 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
510 // Retrieve the text.
511 if(0u != numberOfCharacters)
513 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
517 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
519 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
520 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
522 return static_cast<Dali::LayoutDirection::Type>(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
526 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
530 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
532 if(mUpdateTextDirection)
534 // Operations that can be done only once until the text changes.
535 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
543 // Set the update info to relayout the whole text.
544 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
545 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
547 // Make sure the model is up-to-date before layouting
548 UpdateModel(onlyOnceOperations);
551 Relayouter::DoRelayout(*this,
552 Size(MAX_FLOAT, MAX_FLOAT),
553 static_cast<OperationsMask>(onlyOnceOperations |
554 LAYOUT | REORDER | UPDATE_DIRECTION),
555 naturalSize.GetVectorXY());
557 // Do not do again the only once operations.
558 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
560 // Clear the update info. This info will be set the next time the text is updated.
561 mTextUpdateInfo.Clear();
563 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
564 mTextUpdateInfo.mFullRelayoutNeeded = true;
566 mUpdateTextDirection = false;
569 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
572 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
574 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
575 mTextUpdateInfo.mStartGlyphIndex = 0u;
576 mTextUpdateInfo.mStartLineIndex = 0u;
577 numberOfCharacters = 0u;
579 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
580 if(0u == numberOfParagraphs)
582 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
583 numberOfCharacters = 0u;
585 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
587 // Nothing else to do if there are no paragraphs.
591 // Find the paragraphs to be updated.
592 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
593 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
595 // Text is being added at the end of the current text.
596 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
598 // Text is being added in a new paragraph after the last character of the text.
599 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
600 numberOfCharacters = 0u;
601 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
603 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
604 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
606 // Nothing else to do;
610 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
614 Length numberOfCharactersToUpdate = 0u;
615 if(mTextUpdateInfo.mFullRelayoutNeeded)
617 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
621 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
623 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
624 numberOfCharactersToUpdate,
625 paragraphsToBeUpdated);
628 if(0u != paragraphsToBeUpdated.Count())
630 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
631 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
632 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
634 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
635 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
637 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
638 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
639 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
640 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
642 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
643 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
645 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
649 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
653 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
654 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
657 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
659 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
662 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
664 return ControllerImplModelUpdater::Update(*this, operationsRequired);
667 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
669 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
672 float Controller::Impl::GetDefaultFontLineHeight()
674 FontId defaultFontId = 0u;
675 if(nullptr == mFontDefaults)
677 TextAbstraction::FontDescription fontDescription;
678 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
682 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
685 Text::FontMetrics fontMetrics;
686 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
688 return (fontMetrics.ascender - fontMetrics.descender);
691 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
693 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
695 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
697 RelayoutAllCharacters();
703 bool Controller::Impl::SetDefaultLineSize(float lineSize)
705 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
707 mLayoutEngine.SetDefaultLineSize(lineSize);
709 RelayoutAllCharacters();
715 string Controller::Impl::GetSelectedText()
718 if(EventData::SELECTING == mEventData->mState)
720 RetrieveSelection(text, false);
725 string Controller::Impl::CopyText()
728 RetrieveSelection(text, false);
729 SendSelectionToClipboard(false); // Text not modified
731 mEventData->mUpdateCursorPosition = true;
733 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
738 string Controller::Impl::CutText()
741 RetrieveSelection(text, false);
748 SendSelectionToClipboard(true); // Synchronous call to modify text
749 mOperationsPending = ALL_OPERATIONS;
751 if((0u != mModel->mLogicalModel->mText.Count()) ||
752 !IsPlaceholderAvailable())
754 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
758 PlaceholderHandler::ShowPlaceholderText(*this);
761 mEventData->mUpdateCursorPosition = true;
762 mEventData->mScrollAfterDelete = true;
766 if(nullptr != mEditableControlInterface)
768 mEditableControlInterface->TextChanged(true);
773 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
775 if(nullptr == mEventData)
777 // Nothing to do if there is no text.
781 if(mEventData->mSelectionEnabled && (pStart || pEnd))
783 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
784 uint32_t oldStart = mEventData->mLeftSelectionPosition;
785 uint32_t oldEnd = mEventData->mRightSelectionPosition;
789 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
793 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
796 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
798 ChangeState(EventData::EDITING);
799 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
800 mEventData->mUpdateCursorPosition = true;
804 ChangeState(EventData::SELECTING);
805 mEventData->mUpdateHighlightBox = true;
806 mEventData->mUpdateLeftSelectionPosition = true;
807 mEventData->mUpdateRightSelectionPosition = true;
810 if(mSelectableControlInterface != nullptr)
812 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
817 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
819 if(nullptr == mEventData)
823 return mEventData->mPrimaryCursorPosition;
826 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
828 if(nullptr == mEventData)
830 // Nothing to do if there is no text.
834 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
836 // Nothing for same cursor position.
840 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
841 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
842 mEventData->mPrimaryCursorPosition = std::min(index, length);
843 // If there is no focus, only the value is updated.
846 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
847 uint32_t oldStart = mEventData->mLeftSelectionPosition;
848 uint32_t oldEnd = mEventData->mRightSelectionPosition;
849 ChangeState(EventData::EDITING);
850 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
851 mEventData->mUpdateCursorPosition = true;
853 if(mSelectableControlInterface != nullptr && wasInSelectingState)
855 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
858 ScrollTextToMatchCursor();
861 if(nullptr != mEditableControlInterface)
863 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
869 Uint32Pair Controller::Impl::GetTextSelectionRange() const
875 range.first = mEventData->mLeftSelectionPosition;
876 range.second = mEventData->mRightSelectionPosition;
882 bool Controller::Impl::IsEditable() const
884 return mEventData && mEventData->mEditingEnabled;
887 void Controller::Impl::SetEditable(bool editable)
891 mEventData->mEditingEnabled = editable;
893 if(mEventData->mDecorator)
895 mEventData->mDecorator->SetEditable(editable);
900 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
902 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
904 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
906 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
907 mFontDefaults->mFontDescription.family = newDefaultFont;
915 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
917 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
919 // Nothing to select if handles are in the same place.
920 selectedText.clear();
924 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
926 //Get start and end position of selection
927 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
928 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
930 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
931 const Length numberOfCharacters = utf32Characters.Count();
933 // Validate the start and end selection points
934 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
936 //Get text as a UTF8 string
937 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
939 if(deleteAfterRetrieval) // Only delete text if copied successfully
941 // Keep a copy of the current input style.
942 InputStyle currentInputStyle;
943 currentInputStyle.Copy(mEventData->mInputStyle);
945 // Set as input style the style of the first deleted character.
946 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
948 // Compare if the input style has changed.
949 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
951 if(hasInputStyleChanged)
953 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
954 // Queue the input style changed signal.
955 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
958 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
960 // Mark the paragraphs to be updated.
961 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
963 mTextUpdateInfo.mCharacterIndex = 0;
964 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
965 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
966 mTextUpdateInfo.mClearAll = true;
970 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
971 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
974 // Delete text between handles
975 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
976 Vector<Character>::Iterator last = first + lengthOfSelectedText;
977 utf32Characters.Erase(first, last);
979 // Will show the cursor at the first character of the selection.
980 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
984 // Will show the cursor at the last character of the selection.
985 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
988 mEventData->mDecoratorUpdated = true;
992 void Controller::Impl::SetSelection(int start, int end)
994 uint32_t oldStart = mEventData->mLeftSelectionPosition;
995 uint32_t oldEnd = mEventData->mRightSelectionPosition;
997 mEventData->mLeftSelectionPosition = start;
998 mEventData->mRightSelectionPosition = end;
999 mEventData->mUpdateCursorPosition = true;
1001 if(mSelectableControlInterface != nullptr)
1003 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1007 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1009 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1012 void Controller::Impl::ShowClipboard()
1016 mClipboard.ShowClipboard();
1020 void Controller::Impl::HideClipboard()
1022 if(mClipboard && mClipboardHideEnabled)
1024 mClipboard.HideClipboard();
1028 void Controller::Impl::SetClipboardHideEnable(bool enable)
1030 mClipboardHideEnabled = enable;
1033 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1035 //Send string to clipboard
1036 return (mClipboard && mClipboard.SetItem(source));
1039 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1041 std::string selectedText;
1042 RetrieveSelection(selectedText, deleteAfterSending);
1043 CopyStringToClipboard(selectedText);
1044 ChangeState(EventData::EDITING);
1047 void Controller::Impl::RequestGetTextFromClipboard()
1051 mClipboard.RequestItem();
1055 void Controller::Impl::RepositionSelectionHandles()
1057 SelectionHandleController::Reposition(*this);
1059 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1061 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1064 void Controller::Impl::SetPopupButtons()
1067 * Sets the Popup buttons to be shown depending on State.
1069 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1071 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1074 bool isEditable = IsEditable();
1075 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1077 if(EventData::SELECTING == mEventData->mState)
1079 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1082 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1085 if(!IsClipboardEmpty())
1089 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1091 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1094 if(!mEventData->mAllTextSelected)
1096 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1099 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1101 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1103 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1106 if(!IsClipboardEmpty())
1110 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1112 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1115 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1117 if(!IsClipboardEmpty())
1121 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1123 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1127 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1130 void Controller::Impl::ChangeState(EventData::State newState)
1132 ChangeTextControllerState(*this, newState);
1135 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1136 CursorInfo& cursorInfo)
1138 if(!IsShowingRealText())
1140 // Do not want to use the place-holder text to set the cursor position.
1142 // Use the line's height of the font's family set to set the cursor's size.
1143 // If there is no font's family set, use the default font.
1144 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1146 cursorInfo.lineOffset = 0.f;
1147 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1148 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1151 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1153 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1156 switch(mModel->mHorizontalAlignment)
1158 case Text::HorizontalAlignment::BEGIN:
1162 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1166 cursorInfo.primaryPosition.x = 0.f;
1170 case Text::HorizontalAlignment::CENTER:
1172 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1175 case Text::HorizontalAlignment::END:
1179 cursorInfo.primaryPosition.x = 0.f;
1183 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1189 // Nothing else to do.
1193 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1194 GetCursorPositionParameters parameters;
1195 parameters.visualModel = mModel->mVisualModel;
1196 parameters.logicalModel = mModel->mLogicalModel;
1197 parameters.metrics = mMetrics;
1198 parameters.logical = logical;
1199 parameters.isMultiline = isMultiLine;
1201 float defaultFontLineHeight = GetDefaultFontLineHeight();
1203 Text::GetCursorPosition(parameters,
1204 defaultFontLineHeight,
1207 // Adds Outline offset.
1208 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1209 cursorInfo.primaryPosition.x += outlineWidth;
1210 cursorInfo.primaryPosition.y += outlineWidth;
1211 cursorInfo.secondaryPosition.x += outlineWidth;
1212 cursorInfo.secondaryPosition.y += outlineWidth;
1216 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1218 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1219 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1221 if(0.f > cursorInfo.primaryPosition.x)
1223 cursorInfo.primaryPosition.x = 0.f;
1226 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1227 if(cursorInfo.primaryPosition.x > edgeWidth)
1229 cursorInfo.primaryPosition.x = edgeWidth;
1234 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1236 if(nullptr == mEventData)
1238 // Nothing to do if there is no text input.
1242 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1244 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1245 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1247 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1248 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1250 if(numberOfCharacters > 1u)
1252 const Script script = mModel->mLogicalModel->GetScript(index);
1253 if(HasLigatureMustBreak(script))
1255 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1256 numberOfCharacters = 1u;
1261 while(0u == numberOfCharacters)
1264 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1268 if(index < mEventData->mPrimaryCursorPosition)
1270 cursorIndex -= numberOfCharacters;
1274 cursorIndex += numberOfCharacters;
1277 // Will update the cursor hook position.
1278 mEventData->mUpdateCursorHookPosition = true;
1283 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1285 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1286 if(nullptr == mEventData)
1288 // Nothing to do if there is no text input.
1289 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1293 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1295 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1297 // Sets the cursor position.
1298 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1301 cursorInfo.primaryCursorHeight,
1302 cursorInfo.lineHeight);
1303 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1305 if(mEventData->mUpdateGrabHandlePosition)
1307 // Sets the grab handle position.
1308 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1310 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1311 cursorInfo.lineHeight);
1314 if(cursorInfo.isSecondaryCursor)
1316 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1317 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1318 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1319 cursorInfo.secondaryCursorHeight,
1320 cursorInfo.lineHeight);
1321 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1324 // Set which cursors are active according the state.
1325 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1327 if(cursorInfo.isSecondaryCursor)
1329 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1333 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1338 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1341 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1344 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1345 const CursorInfo& cursorInfo)
1347 SelectionHandleController::Update(*this, handleType, cursorInfo);
1350 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1352 // Clamp between -space & -alignment offset.
1354 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1356 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1357 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1358 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1360 mEventData->mDecoratorUpdated = true;
1364 mModel->mScrollPosition.x = 0.f;
1368 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1370 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1372 // Nothing to do if the text is single line.
1376 // Clamp between -space & 0.
1377 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1379 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1380 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1381 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1383 mEventData->mDecoratorUpdated = true;
1387 mModel->mScrollPosition.y = 0.f;
1391 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1393 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1395 // position is in actor's coords.
1396 const float positionEndX = position.x + cursorWidth;
1397 const float positionEndY = position.y + lineHeight;
1399 // Transform the position to decorator coords.
1400 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1401 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1403 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1404 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1406 if(decoratorPositionBeginX < 0.f)
1408 mModel->mScrollPosition.x = -position.x;
1410 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1412 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1415 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1417 if(decoratorPositionBeginY < 0.f)
1419 mModel->mScrollPosition.y = -position.y;
1421 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1423 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1428 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1430 // Get the current cursor position in decorator coords.
1431 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1433 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1435 // Calculate the offset to match the cursor position before the character was deleted.
1436 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1438 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1439 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1441 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1442 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1445 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1446 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1448 // Makes the new cursor position visible if needed.
1449 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1452 void Controller::Impl::ScrollTextToMatchCursor()
1454 CursorInfo cursorInfo;
1455 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1456 ScrollTextToMatchCursor(cursorInfo);
1459 void Controller::Impl::RequestRelayout()
1461 if(nullptr != mControlInterface)
1463 mControlInterface->RequestTextRelayout();
1467 void Controller::Impl::RelayoutAllCharacters()
1469 // relayout all characters
1470 mTextUpdateInfo.mCharacterIndex = 0;
1471 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1472 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1473 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1475 mTextUpdateInfo.mFullRelayoutNeeded = true;
1477 // Need to recalculate natural size
1478 mRecalculateNaturalSize = true;
1481 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1483 ChangeState(EventData::EDITING);
1489 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1491 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1494 void Controller::Impl::ProcessInputStyleChangedSignals()
1498 if(mEditableControlInterface)
1500 // Emit the input style changed signal for each mask
1501 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1502 mEventData->mInputStyleChangedQueue.end(),
1503 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1506 mEventData->mInputStyleChangedQueue.Clear();
1510 void Controller::Impl::ScrollBy(Vector2 scroll)
1512 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1514 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1515 const Vector2 currentScroll = mModel->mScrollPosition;
1517 scroll.x = -scroll.x;
1518 scroll.y = -scroll.y;
1520 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1522 mModel->mScrollPosition.x += scroll.x;
1523 ClampHorizontalScroll(layoutSize);
1526 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1528 mModel->mScrollPosition.y += scroll.y;
1529 ClampVerticalScroll(layoutSize);
1532 if(mModel->mScrollPosition != currentScroll)
1534 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1540 float Controller::Impl::GetHorizontalScrollPosition()
1542 // Scroll values are negative internally so we convert them to positive numbers
1543 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1546 float Controller::Impl::GetVerticalScrollPosition()
1548 // Scroll values are negative internally so we convert them to positive numbers
1549 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1552 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1555 return Vector3(10.f, 10.f, 10.f);
1558 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1561 return Vector2(10.f, 10.f);
1564 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1566 auto actor = Toolkit::TextAnchor::New();
1567 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1568 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1569 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1570 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1571 const Vector2 anchorSize = GetAnchorSize(anchor);
1572 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1573 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1574 actor.SetProperty(Actor::Property::NAME, anchorText);
1575 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1576 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1577 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1581 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1583 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1584 we need to create and destroy potentially many actors. Some optimization can be considered here.
1585 Maybe a "dirty" flag in mLogicalModel? */
1586 anchorActors.clear();
1587 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1589 auto actor = CreateAnchorActor(anchor);
1590 anchorActors.push_back(actor);
1594 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1596 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1598 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1603 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1606 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1608 //Underlined character runs for markup-processor
1609 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1610 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1611 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1613 if(shouldClearPreUnderlineRuns)
1615 mModel->mVisualModel->mUnderlineRuns.Clear();
1618 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1620 CharacterIndex characterIndex = it->characterRun.characterIndex;
1621 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1622 for(Length index = 0u; index < numberOfCharacters; index++)
1624 UnderlinedGlyphRun underlineGlyphRun;
1625 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex + index];
1626 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex + index];
1628 //Copy properties (attributes)
1629 underlineGlyphRun.properties.type = it->properties.type;
1630 underlineGlyphRun.properties.color = it->properties.color;
1631 underlineGlyphRun.properties.height = it->properties.height;
1632 underlineGlyphRun.properties.dashGap = it->properties.dashGap;
1633 underlineGlyphRun.properties.dashWidth = it->properties.dashWidth;
1634 underlineGlyphRun.properties.typeDefined = it->properties.typeDefined;
1635 underlineGlyphRun.properties.colorDefined = it->properties.colorDefined;
1636 underlineGlyphRun.properties.heightDefined = it->properties.heightDefined;
1637 underlineGlyphRun.properties.dashGapDefined = it->properties.dashGapDefined;
1638 underlineGlyphRun.properties.dashWidthDefined = it->properties.dashWidthDefined;
1640 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1645 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1647 //Strikethrough character runs from markup-processor
1648 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1649 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1650 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1652 mModel->mVisualModel->mStrikethroughRuns.Clear();
1654 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1656 CharacterIndex characterIndex = it->characterRun.characterIndex;
1657 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1659 if(numberOfCharacters == 0)
1664 StrikethroughGlyphRun strikethroughGlyphRun;
1665 strikethroughGlyphRun.color = it->color;
1666 strikethroughGlyphRun.isColorSet = it->isColorSet;
1667 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1668 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1670 for(Length index = 1u; index < numberOfCharacters; index++)
1672 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1675 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1679 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1681 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1683 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1686 UPDATE_LAYOUT_SIZE |
1691 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1692 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1696 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1699 mIsAutoScrollEnabled = enable;
1704 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1705 mIsAutoScrollEnabled = false;
1709 void Controller::Impl::SetEnableCursorBlink(bool enable)
1711 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1715 mEventData->mCursorBlinkEnabled = enable;
1717 if(!enable && mEventData->mDecorator)
1719 mEventData->mDecorator->StopCursorBlink();
1724 void Controller::Impl::SetMultiLineEnabled(bool enable)
1726 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1728 if(layout != mLayoutEngine.GetLayout())
1730 // Set the layout type.
1731 mLayoutEngine.SetLayout(layout);
1733 // Set the flags to redo the layout operations
1734 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1735 UPDATE_LAYOUT_SIZE |
1739 mTextUpdateInfo.mFullRelayoutNeeded = true;
1740 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1742 // Need to recalculate natural size
1743 mRecalculateNaturalSize = true;
1749 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1751 if(alignment != mModel->mHorizontalAlignment)
1753 // Set the alignment.
1754 mModel->mHorizontalAlignment = alignment;
1756 // Set the flag to redo the alignment operation.
1757 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1761 mEventData->mUpdateAlignment = true;
1763 // Update the cursor if it's in editing mode
1764 if(EventData::IsEditingState(mEventData->mState))
1766 ChangeState(EventData::EDITING);
1767 mEventData->mUpdateCursorPosition = true;
1775 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1777 if(alignment != mModel->mVerticalAlignment)
1779 // Set the alignment.
1780 mModel->mVerticalAlignment = alignment;
1781 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | ALIGN);
1786 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1788 if(lineWrapMode != mModel->mLineWrapMode)
1790 // Update Text layout for applying wrap mode
1791 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1794 UPDATE_LAYOUT_SIZE |
1797 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1798 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1800 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1803 // Set the text wrap mode.
1804 mModel->mLineWrapMode = lineWrapMode;
1806 mTextUpdateInfo.mCharacterIndex = 0u;
1807 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1808 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1815 void Controller::Impl::SetDefaultColor(const Vector4& color)
1819 if(!IsShowingPlaceholderText())
1821 mModel->mVisualModel->SetTextColor(color);
1822 mModel->mLogicalModel->mColorRuns.Clear();
1823 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1828 void Controller::Impl::ClearFontData()
1832 mFontDefaults->mFontId = 0u; // Remove old font ID
1835 // Set flags to update the model.
1836 mTextUpdateInfo.mCharacterIndex = 0u;
1837 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1838 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1840 mTextUpdateInfo.mClearAll = true;
1841 mTextUpdateInfo.mFullRelayoutNeeded = true;
1842 mRecalculateNaturalSize = true;
1844 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1850 UPDATE_LAYOUT_SIZE |
1855 void Controller::Impl::ClearStyleData()
1857 mModel->mLogicalModel->mColorRuns.Clear();
1858 mModel->mLogicalModel->ClearFontDescriptionRuns();
1859 mModel->mLogicalModel->ClearStrikethroughRuns();
1862 void Controller::Impl::ResetScrollPosition()
1866 // Reset the scroll position.
1867 mModel->mScrollPosition = Vector2::ZERO;
1868 mEventData->mScrollAfterUpdatePosition = true;
1872 } // namespace Dali::Toolkit::Text