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/controller/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/controller/text-controller-impl-data-clearer.h>
34 #include <dali-toolkit/internal/text/controller/text-controller-impl-event-handler.h>
35 #include <dali-toolkit/internal/text/controller/text-controller-impl-model-updater.h>
36 #include <dali-toolkit/internal/text/controller/text-controller-placeholder-handler.h>
37 #include <dali-toolkit/internal/text/controller/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 char* 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 void UpdateCursorPositionForAlignment(Controller::Impl& impl, bool needFullAlignment)
367 EventData* eventData = impl.mEventData;
369 // Set the flag to redo the alignment operation
370 impl.mOperationsPending = static_cast<Controller::OperationsMask>(impl.mOperationsPending | Controller::OperationsMask::ALIGN);
374 // Note: mUpdateAlignment is currently only needed for horizontal alignment
375 eventData->mUpdateAlignment = needFullAlignment;
377 // Update the cursor if it's in editing mode
378 if(EventData::IsEditingState(eventData->mState))
380 impl.ChangeState(EventData::EDITING);
381 eventData->mUpdateCursorPosition = true;
386 } // unnamed Namespace
388 EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext)
389 : mDecorator(decorator),
390 mInputMethodContext(inputMethodContext),
391 mPlaceholderFont(nullptr),
392 mPlaceholderTextActive(),
393 mPlaceholderTextInactive(),
394 mPlaceholderTextColor(0.8f, 0.8f, 0.8f, 0.8f), // This color has been published in the Public API (placeholder-properties.h).
396 mInputStyleChangedQueue(),
397 mPreviousState(INACTIVE),
399 mPrimaryCursorPosition(0u),
400 mLeftSelectionPosition(0u),
401 mRightSelectionPosition(0u),
402 mPreEditStartPosition(0u),
404 mCursorHookPositionX(0.f),
405 mDoubleTapAction(Controller::NoTextTap::NO_ACTION),
406 mLongPressAction(Controller::NoTextTap::SHOW_SELECTION_POPUP),
407 mIsShowingPlaceholderText(false),
409 mDecoratorUpdated(false),
410 mCursorBlinkEnabled(true),
411 mGrabHandleEnabled(true),
412 mGrabHandlePopupEnabled(true),
413 mSelectionEnabled(true),
414 mUpdateCursorHookPosition(false),
415 mUpdateCursorPosition(false),
416 mUpdateGrabHandlePosition(false),
417 mUpdateLeftSelectionPosition(false),
418 mUpdateRightSelectionPosition(false),
419 mIsLeftHandleSelected(false),
420 mIsRightHandleSelected(false),
421 mUpdateHighlightBox(false),
422 mScrollAfterUpdatePosition(false),
423 mScrollAfterDelete(false),
424 mAllTextSelected(false),
425 mUpdateInputStyle(false),
426 mPasswordInput(false),
427 mCheckScrollAmount(false),
428 mIsPlaceholderPixelSize(false),
429 mIsPlaceholderElideEnabled(false),
430 mPlaceholderEllipsisFlag(false),
431 mShiftSelectionFlag(true),
432 mUpdateAlignment(false),
433 mEditingEnabled(true)
437 bool Controller::Impl::ProcessInputEvents()
439 return ControllerImplEventHandler::ProcessInputEvents(*this);
442 void Controller::Impl::NotifyInputMethodContext()
444 if(mEventData && mEventData->mInputMethodContext)
446 CharacterIndex cursorPosition = GetLogicalCursorPosition();
447 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
448 mEventData->mInputMethodContext.NotifyCursorPosition();
452 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
454 if(mEventData && mEventData->mInputMethodContext)
456 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
457 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
461 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
463 CharacterIndex cursorPosition = 0u;
467 if((EventData::SELECTING == mEventData->mState) ||
468 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
470 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
474 cursorPosition = mEventData->mPrimaryCursorPosition;
478 return cursorPosition;
481 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
483 Length numberOfWhiteSpaces = 0u;
485 // Get the buffer to the text.
486 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
488 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
489 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
491 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
497 return numberOfWhiteSpaces;
500 void Controller::Impl::GetText(std::string& text) const
502 if(!IsShowingPlaceholderText())
504 // Retrieves the text string.
509 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
513 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
515 // Get the total number of characters.
516 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
518 // Retrieve the text.
519 if(0u != numberOfCharacters)
521 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
525 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
527 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
528 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
530 Window window = DevelWindow::Get(actor);
531 return static_cast<Dali::LayoutDirection::Type>(window ? window.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
535 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
539 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
541 if(mUpdateTextDirection)
543 // Operations that can be done only once until the text changes.
544 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
552 // Set the update info to relayout the whole text.
553 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
554 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
556 // Make sure the model is up-to-date before layouting
557 UpdateModel(onlyOnceOperations);
560 Relayouter::DoRelayout(*this,
561 Size(MAX_FLOAT, MAX_FLOAT),
562 static_cast<OperationsMask>(onlyOnceOperations |
563 LAYOUT | REORDER | UPDATE_DIRECTION),
564 naturalSize.GetVectorXY());
566 // Do not do again the only once operations.
567 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
569 // Clear the update info. This info will be set the next time the text is updated.
570 mTextUpdateInfo.Clear();
572 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
573 mTextUpdateInfo.mFullRelayoutNeeded = true;
575 mUpdateTextDirection = false;
578 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
581 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
583 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
584 mTextUpdateInfo.mStartGlyphIndex = 0u;
585 mTextUpdateInfo.mStartLineIndex = 0u;
586 numberOfCharacters = 0u;
588 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
589 if(0u == numberOfParagraphs)
591 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
592 numberOfCharacters = 0u;
594 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
596 // Nothing else to do if there are no paragraphs.
600 // Find the paragraphs to be updated.
601 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
602 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
604 // Text is being added at the end of the current text.
605 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
607 // Text is being added in a new paragraph after the last character of the text.
608 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
609 numberOfCharacters = 0u;
610 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
612 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
613 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
615 // Nothing else to do;
619 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
623 Length numberOfCharactersToUpdate = 0u;
624 if(mTextUpdateInfo.mFullRelayoutNeeded)
626 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
630 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
632 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
633 numberOfCharactersToUpdate,
634 paragraphsToBeUpdated);
637 if(0u != paragraphsToBeUpdated.Count())
639 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
640 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
641 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
643 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
644 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
646 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
647 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
648 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
649 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
651 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
652 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
654 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
658 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
662 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
663 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
666 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
668 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
671 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
673 return ControllerImplModelUpdater::Update(*this, operationsRequired);
676 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
678 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
681 float Controller::Impl::GetDefaultFontLineHeight()
683 FontId defaultFontId = 0u;
684 if(nullptr == mFontDefaults)
686 TextAbstraction::FontDescription fontDescription;
687 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
691 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
694 Text::FontMetrics fontMetrics;
695 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
697 return (fontMetrics.ascender - fontMetrics.descender);
700 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
702 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
704 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
706 RelayoutAllCharacters();
712 bool Controller::Impl::SetDefaultLineSize(float lineSize)
714 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
716 mLayoutEngine.SetDefaultLineSize(lineSize);
718 RelayoutAllCharacters();
724 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
726 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
728 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
730 RelayoutAllCharacters();
736 float Controller::Impl::GetRelativeLineSize()
738 return mLayoutEngine.GetRelativeLineSize();
741 string Controller::Impl::GetSelectedText()
744 if(EventData::SELECTING == mEventData->mState)
746 RetrieveSelection(text, false);
751 string Controller::Impl::CopyText()
754 RetrieveSelection(text, false);
755 SendSelectionToClipboard(false); // Text not modified
757 mEventData->mUpdateCursorPosition = true;
759 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
764 string Controller::Impl::CutText()
767 RetrieveSelection(text, false);
774 SendSelectionToClipboard(true); // Synchronous call to modify text
775 mOperationsPending = ALL_OPERATIONS;
777 if((0u != mModel->mLogicalModel->mText.Count()) ||
778 !IsPlaceholderAvailable())
780 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
784 PlaceholderHandler::ShowPlaceholderText(*this);
787 mEventData->mUpdateCursorPosition = true;
788 mEventData->mScrollAfterDelete = true;
792 if(nullptr != mEditableControlInterface)
794 mEditableControlInterface->TextChanged(true);
799 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
801 if(nullptr == mEventData)
803 // Nothing to do if there is no text.
807 if(mEventData->mSelectionEnabled && (pStart || pEnd))
809 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
810 uint32_t oldStart = mEventData->mLeftSelectionPosition;
811 uint32_t oldEnd = mEventData->mRightSelectionPosition;
815 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
819 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
822 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
824 ChangeState(EventData::EDITING);
825 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
826 mEventData->mUpdateCursorPosition = true;
830 ChangeState(EventData::SELECTING);
831 mEventData->mUpdateHighlightBox = true;
832 mEventData->mUpdateLeftSelectionPosition = true;
833 mEventData->mUpdateRightSelectionPosition = true;
836 if(mSelectableControlInterface != nullptr)
838 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
843 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
845 if(nullptr == mEventData)
849 return mEventData->mPrimaryCursorPosition;
852 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
854 if(nullptr == mEventData)
856 // Nothing to do if there is no text.
860 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
862 // Nothing for same cursor position.
866 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
867 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
868 mEventData->mPrimaryCursorPosition = std::min(index, length);
869 // If there is no focus, only the value is updated.
872 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
873 uint32_t oldStart = mEventData->mLeftSelectionPosition;
874 uint32_t oldEnd = mEventData->mRightSelectionPosition;
875 ChangeState(EventData::EDITING);
876 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
877 mEventData->mUpdateCursorPosition = true;
879 if(mSelectableControlInterface != nullptr && wasInSelectingState)
881 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
884 ScrollTextToMatchCursor();
887 if(nullptr != mEditableControlInterface)
889 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
895 Uint32Pair Controller::Impl::GetTextSelectionRange() const
901 range.first = mEventData->mLeftSelectionPosition;
902 range.second = mEventData->mRightSelectionPosition;
908 bool Controller::Impl::IsEditable() const
910 return mEventData && mEventData->mEditingEnabled;
913 void Controller::Impl::SetEditable(bool editable)
917 mEventData->mEditingEnabled = editable;
919 if(mEventData->mDecorator)
921 bool decoratorEditable = editable && mIsUserInteractionEnabled;
922 mEventData->mDecorator->SetEditable(decoratorEditable);
927 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
929 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
931 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
933 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
934 mFontDefaults->mFontDescription.family = newDefaultFont;
942 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
944 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
946 // Nothing to select if handles are in the same place.
947 selectedText.clear();
951 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
953 //Get start and end position of selection
954 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
955 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
957 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
958 const Length numberOfCharacters = utf32Characters.Count();
960 // Validate the start and end selection points
961 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
963 //Get text as a UTF8 string
964 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
966 if(deleteAfterRetrieval) // Only delete text if copied successfully
968 // Keep a copy of the current input style.
969 InputStyle currentInputStyle;
970 currentInputStyle.Copy(mEventData->mInputStyle);
972 // Set as input style the style of the first deleted character.
973 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
975 // Compare if the input style has changed.
976 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
978 if(hasInputStyleChanged)
980 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
981 // Queue the input style changed signal.
982 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
985 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
987 // Mark the paragraphs to be updated.
988 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
990 mTextUpdateInfo.mCharacterIndex = 0;
991 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
992 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
993 mTextUpdateInfo.mClearAll = true;
997 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
998 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1001 // Delete text between handles
1002 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1003 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1004 utf32Characters.Erase(first, last);
1006 // Will show the cursor at the first character of the selection.
1007 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1011 // Will show the cursor at the last character of the selection.
1012 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1015 mEventData->mDecoratorUpdated = true;
1019 void Controller::Impl::SetSelection(int start, int end)
1021 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1022 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1024 mEventData->mLeftSelectionPosition = start;
1025 mEventData->mRightSelectionPosition = end;
1026 mEventData->mUpdateCursorPosition = true;
1028 if(mSelectableControlInterface != nullptr)
1030 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1034 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1036 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1039 void Controller::Impl::ShowClipboard()
1041 if(EnsureClipboardCreated())
1043 mClipboard.ShowClipboard();
1047 void Controller::Impl::HideClipboard()
1049 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1051 mClipboard.HideClipboard();
1055 void Controller::Impl::SetClipboardHideEnable(bool enable)
1057 mClipboardHideEnabled = enable;
1060 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1062 //Send string to clipboard
1063 return (EnsureClipboardCreated() && mClipboard.SetItem(source));
1066 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1068 std::string selectedText;
1069 RetrieveSelection(selectedText, deleteAfterSending);
1070 CopyStringToClipboard(selectedText);
1071 ChangeState(EventData::EDITING);
1074 void Controller::Impl::RequestGetTextFromClipboard()
1076 if(EnsureClipboardCreated())
1078 mClipboard.RequestItem();
1082 void Controller::Impl::RepositionSelectionHandles()
1084 SelectionHandleController::Reposition(*this);
1086 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1088 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1091 void Controller::Impl::SetPopupButtons()
1094 * Sets the Popup buttons to be shown depending on State.
1096 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1098 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1101 bool isEditable = IsEditable();
1102 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1104 if(EventData::SELECTING == mEventData->mState)
1106 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1109 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1112 if(!IsClipboardEmpty())
1116 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1118 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1121 if(!mEventData->mAllTextSelected)
1123 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1126 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1128 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1130 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1133 if(!IsClipboardEmpty())
1137 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1139 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1142 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1144 if(!IsClipboardEmpty())
1148 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1150 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1154 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1157 void Controller::Impl::ChangeState(EventData::State newState)
1159 ChangeTextControllerState(*this, newState);
1162 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1163 CursorInfo& cursorInfo)
1165 if(!IsShowingRealText())
1167 // Do not want to use the place-holder text to set the cursor position.
1169 // Use the line's height of the font's family set to set the cursor's size.
1170 // If there is no font's family set, use the default font.
1171 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1173 cursorInfo.lineOffset = 0.f;
1174 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1175 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1178 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1180 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1183 switch(mModel->mHorizontalAlignment)
1185 case Text::HorizontalAlignment::BEGIN:
1189 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1193 cursorInfo.primaryPosition.x = 0.f;
1197 case Text::HorizontalAlignment::CENTER:
1199 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1202 case Text::HorizontalAlignment::END:
1206 cursorInfo.primaryPosition.x = 0.f;
1210 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1216 // Nothing else to do.
1220 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1221 GetCursorPositionParameters parameters;
1222 parameters.visualModel = mModel->mVisualModel;
1223 parameters.logicalModel = mModel->mLogicalModel;
1224 parameters.metrics = mMetrics;
1225 parameters.logical = logical;
1226 parameters.isMultiline = isMultiLine;
1228 float defaultFontLineHeight = GetDefaultFontLineHeight();
1230 Text::GetCursorPosition(parameters,
1231 defaultFontLineHeight,
1234 // Adds Outline offset.
1235 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1236 cursorInfo.primaryPosition.x += outlineWidth;
1237 cursorInfo.primaryPosition.y += outlineWidth;
1238 cursorInfo.secondaryPosition.x += outlineWidth;
1239 cursorInfo.secondaryPosition.y += outlineWidth;
1243 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1245 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1246 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1248 if(0.f > cursorInfo.primaryPosition.x)
1250 cursorInfo.primaryPosition.x = 0.f;
1253 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1254 if(cursorInfo.primaryPosition.x > edgeWidth)
1256 cursorInfo.primaryPosition.x = edgeWidth;
1261 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1263 if(nullptr == mEventData)
1265 // Nothing to do if there is no text input.
1269 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1271 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1272 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1274 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1275 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1277 if(numberOfCharacters > 1u)
1279 const Script script = mModel->mLogicalModel->GetScript(index);
1280 if(HasLigatureMustBreak(script))
1282 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1283 numberOfCharacters = 1u;
1288 while(0u == numberOfCharacters)
1291 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1295 if(index < mEventData->mPrimaryCursorPosition)
1297 cursorIndex -= numberOfCharacters;
1301 cursorIndex += numberOfCharacters;
1304 // Will update the cursor hook position.
1305 mEventData->mUpdateCursorHookPosition = true;
1310 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1312 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1313 if(nullptr == mEventData)
1315 // Nothing to do if there is no text input.
1316 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1320 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1322 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1324 // Sets the cursor position.
1325 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1328 cursorInfo.primaryCursorHeight,
1329 cursorInfo.lineHeight);
1330 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1332 if(mEventData->mUpdateGrabHandlePosition)
1334 // Sets the grab handle position.
1335 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1337 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1338 cursorInfo.lineHeight);
1341 if(cursorInfo.isSecondaryCursor)
1343 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1344 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1345 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1346 cursorInfo.secondaryCursorHeight,
1347 cursorInfo.lineHeight);
1348 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1351 // Set which cursors are active according the state.
1352 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1354 if(cursorInfo.isSecondaryCursor)
1356 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1360 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1365 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1368 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1371 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1372 const CursorInfo& cursorInfo)
1374 SelectionHandleController::Update(*this, handleType, cursorInfo);
1377 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1379 // Clamp between -space & -alignment offset.
1381 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1383 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1384 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1385 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1387 mEventData->mDecoratorUpdated = true;
1391 mModel->mScrollPosition.x = 0.f;
1395 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1397 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1399 // Nothing to do if the text is single line.
1403 // Clamp between -space & 0.
1404 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1406 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1407 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1408 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1410 mEventData->mDecoratorUpdated = true;
1414 mModel->mScrollPosition.y = 0.f;
1418 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1420 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1422 // position is in actor's coords.
1423 const float positionEndX = position.x + cursorWidth;
1424 const float positionEndY = position.y + lineHeight;
1426 // Transform the position to decorator coords.
1427 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1428 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1430 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1431 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1433 if(decoratorPositionBeginX < 0.f)
1435 mModel->mScrollPosition.x = -position.x;
1437 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1439 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1442 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1444 if(decoratorPositionBeginY < 0.f)
1446 mModel->mScrollPosition.y = -position.y;
1448 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1450 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1452 else if(mModel->mLogicalModel->mText.Count() == 0u)
1454 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1459 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1461 // Get the current cursor position in decorator coords.
1462 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1464 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1466 // Calculate the offset to match the cursor position before the character was deleted.
1467 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1469 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1470 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1472 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1473 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1476 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1477 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1479 // Makes the new cursor position visible if needed.
1480 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1483 void Controller::Impl::ScrollTextToMatchCursor()
1485 CursorInfo cursorInfo;
1486 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1487 ScrollTextToMatchCursor(cursorInfo);
1490 void Controller::Impl::RequestRelayout()
1492 if(nullptr != mControlInterface)
1494 mControlInterface->RequestTextRelayout();
1498 void Controller::Impl::RelayoutAllCharacters()
1500 // relayout all characters
1501 mTextUpdateInfo.mCharacterIndex = 0;
1502 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1503 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1504 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1506 mTextUpdateInfo.mFullRelayoutNeeded = true;
1508 // Need to recalculate natural size
1509 mRecalculateNaturalSize = true;
1512 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1514 ChangeState(EventData::EDITING);
1520 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1522 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1525 void Controller::Impl::ProcessInputStyleChangedSignals()
1529 if(mEditableControlInterface)
1531 // Emit the input style changed signal for each mask
1532 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1533 mEventData->mInputStyleChangedQueue.end(),
1534 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1537 mEventData->mInputStyleChangedQueue.Clear();
1541 void Controller::Impl::ScrollBy(Vector2 scroll)
1543 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1545 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1546 const Vector2 currentScroll = mModel->mScrollPosition;
1548 scroll.x = -scroll.x;
1549 scroll.y = -scroll.y;
1551 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1553 mModel->mScrollPosition.x += scroll.x;
1554 ClampHorizontalScroll(layoutSize);
1557 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1559 mModel->mScrollPosition.y += scroll.y;
1560 ClampVerticalScroll(layoutSize);
1563 if(mModel->mScrollPosition != currentScroll)
1565 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1571 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1573 bool isScrollable = false;
1576 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1577 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1578 if(isHorizontalScrollEnabled ||isVerticalScrollEnabled)
1580 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1581 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1582 const Vector2& scrollPosition = mModel->mScrollPosition;
1584 if(isHorizontalScrollEnabled)
1586 const float displacementX = displacement.x;
1587 const float positionX = scrollPosition.x + displacementX;
1588 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1590 isScrollable = true;
1594 if(isVerticalScrollEnabled)
1596 const float displacementY = displacement.y;
1597 const float positionY = scrollPosition.y + displacementY;
1598 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1600 isScrollable = true;
1605 return isScrollable;
1608 float Controller::Impl::GetHorizontalScrollPosition()
1610 // Scroll values are negative internally so we convert them to positive numbers
1611 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1614 float Controller::Impl::GetVerticalScrollPosition()
1616 // Scroll values are negative internally so we convert them to positive numbers
1617 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1620 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1623 return Vector3(10.f, 10.f, 10.f);
1626 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1629 return Vector2(10.f, 10.f);
1632 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1634 auto actor = Toolkit::TextAnchor::New();
1635 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1636 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1637 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1638 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1639 const Vector2 anchorSize = GetAnchorSize(anchor);
1640 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1641 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1642 actor.SetProperty(Actor::Property::NAME, anchorText);
1643 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1644 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1645 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1649 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1651 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1652 we need to create and destroy potentially many actors. Some optimization can be considered here.
1653 Maybe a "dirty" flag in mLogicalModel? */
1654 anchorActors.clear();
1655 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1657 auto actor = CreateAnchorActor(anchor);
1658 anchorActors.push_back(actor);
1662 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1664 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1666 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1671 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1674 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1676 //Underlined character runs for markup-processor
1677 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1678 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1679 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1681 if(shouldClearPreUnderlineRuns)
1683 mModel->mVisualModel->mUnderlineRuns.Clear();
1686 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1688 CharacterIndex characterIndex = it->characterRun.characterIndex;
1689 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1691 if(numberOfCharacters == 0)
1696 // Create one run for all glyphs of all run's characters that has same properties
1697 // This enhance performance and reduce the needed memory to store glyphs-runs
1698 UnderlinedGlyphRun underlineGlyphRun;
1699 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1700 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1701 //Copy properties (attributes)
1702 underlineGlyphRun.properties = it->properties;
1704 for(Length index = 1u; index < numberOfCharacters; index++)
1706 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1709 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1713 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1715 //Strikethrough character runs from markup-processor
1716 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1717 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1718 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1720 mModel->mVisualModel->mStrikethroughRuns.Clear();
1722 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1724 CharacterIndex characterIndex = it->characterRun.characterIndex;
1725 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1727 if(numberOfCharacters == 0)
1732 StrikethroughGlyphRun strikethroughGlyphRun;
1733 strikethroughGlyphRun.properties = it->properties;
1734 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1735 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1737 for(Length index = 1u; index < numberOfCharacters; index++)
1739 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1742 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1746 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1748 //CharacterSpacing character runs from markup-processor
1749 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1750 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1751 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1753 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1755 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1757 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1758 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1760 if(numberOfCharacters == 0)
1765 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1766 characterSpacingGlyphRun.value = it->value;
1767 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1768 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1770 for(Length index = 1u; index < numberOfCharacters; index++)
1772 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1775 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1779 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1781 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1783 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1786 UPDATE_LAYOUT_SIZE |
1791 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1792 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1796 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1799 mIsAutoScrollEnabled = enable;
1804 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1805 mIsAutoScrollEnabled = false;
1809 void Controller::Impl::SetEnableCursorBlink(bool enable)
1811 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1815 mEventData->mCursorBlinkEnabled = enable;
1817 if(!enable && mEventData->mDecorator)
1819 mEventData->mDecorator->StopCursorBlink();
1824 void Controller::Impl::SetMultiLineEnabled(bool enable)
1826 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1828 if(layout != mLayoutEngine.GetLayout())
1830 // Set the layout type.
1831 mLayoutEngine.SetLayout(layout);
1833 // Set the flags to redo the layout operations
1834 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1835 UPDATE_LAYOUT_SIZE |
1839 mTextUpdateInfo.mFullRelayoutNeeded = true;
1840 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1842 // Need to recalculate natural size
1843 mRecalculateNaturalSize = true;
1849 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1851 if(alignment != mModel->mHorizontalAlignment)
1853 // Set the alignment.
1854 mModel->mHorizontalAlignment = alignment;
1855 UpdateCursorPositionForAlignment(*this, true);
1860 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1862 if(alignment != mModel->mVerticalAlignment)
1864 // Set the alignment.
1865 mModel->mVerticalAlignment = alignment;
1866 UpdateCursorPositionForAlignment(*this, false);
1871 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1873 if(lineWrapMode != mModel->mLineWrapMode)
1875 // Update Text layout for applying wrap mode
1876 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1879 UPDATE_LAYOUT_SIZE |
1882 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1883 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1885 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1888 // Set the text wrap mode.
1889 mModel->mLineWrapMode = lineWrapMode;
1891 mTextUpdateInfo.mCharacterIndex = 0u;
1892 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1893 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1900 void Controller::Impl::SetDefaultColor(const Vector4& color)
1904 if(!IsShowingPlaceholderText())
1906 mModel->mVisualModel->SetTextColor(color);
1907 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1912 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1914 mIsUserInteractionEnabled = enabled;
1916 if(mEventData && mEventData->mDecorator)
1918 bool editable = mEventData->mEditingEnabled && enabled;
1919 mEventData->mDecorator->SetEditable(editable);
1923 void Controller::Impl::ClearFontData()
1927 mFontDefaults->mFontId = 0u; // Remove old font ID
1930 // Set flags to update the model.
1931 mTextUpdateInfo.mCharacterIndex = 0u;
1932 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1933 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1935 mTextUpdateInfo.mClearAll = true;
1936 mTextUpdateInfo.mFullRelayoutNeeded = true;
1937 mRecalculateNaturalSize = true;
1939 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1945 UPDATE_LAYOUT_SIZE |
1950 void Controller::Impl::ClearStyleData()
1952 mModel->mLogicalModel->mColorRuns.Clear();
1953 mModel->mLogicalModel->ClearFontDescriptionRuns();
1954 mModel->mLogicalModel->ClearStrikethroughRuns();
1957 void Controller::Impl::ResetScrollPosition()
1961 // Reset the scroll position.
1962 mModel->mScrollPosition = Vector2::ZERO;
1963 mEventData->mScrollAfterUpdatePosition = true;
1967 } // namespace Dali::Toolkit::Text