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 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 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();
448 const Length numberOfWhiteSpaces = GetNumberOfWhiteSpaces(0u);
450 // Update the cursor position by removing the initial white spaces.
451 if(cursorPosition < numberOfWhiteSpaces)
457 cursorPosition -= numberOfWhiteSpaces;
460 mEventData->mInputMethodContext.SetCursorPosition(cursorPosition);
461 mEventData->mInputMethodContext.NotifyCursorPosition();
465 void Controller::Impl::NotifyInputMethodContextMultiLineStatus()
467 if(mEventData && mEventData->mInputMethodContext)
469 Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout();
470 mEventData->mInputMethodContext.NotifyTextInputMultiLine(layout == Text::Layout::Engine::MULTI_LINE_BOX);
474 CharacterIndex Controller::Impl::GetLogicalCursorPosition() const
476 CharacterIndex cursorPosition = 0u;
480 if((EventData::SELECTING == mEventData->mState) ||
481 (EventData::SELECTION_HANDLE_PANNING == mEventData->mState))
483 cursorPosition = std::min(mEventData->mRightSelectionPosition, mEventData->mLeftSelectionPosition);
487 cursorPosition = mEventData->mPrimaryCursorPosition;
491 return cursorPosition;
494 Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const
496 Length numberOfWhiteSpaces = 0u;
498 // Get the buffer to the text.
499 Character* utf32CharacterBuffer = mModel->mLogicalModel->mText.Begin();
501 const Length totalNumberOfCharacters = mModel->mLogicalModel->mText.Count();
502 for(; index < totalNumberOfCharacters; ++index, ++numberOfWhiteSpaces)
504 if(!TextAbstraction::IsWhiteSpace(*(utf32CharacterBuffer + index)))
510 return numberOfWhiteSpaces;
513 void Controller::Impl::GetText(std::string& text) const
515 if(!IsShowingPlaceholderText())
517 // Retrieves the text string.
522 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this);
526 void Controller::Impl::GetText(CharacterIndex index, std::string& text) const
528 // Get the total number of characters.
529 Length numberOfCharacters = mModel->mLogicalModel->mText.Count();
531 // Retrieve the text.
532 if(0u != numberOfCharacters)
534 Utf32ToUtf8(mModel->mLogicalModel->mText.Begin() + index, numberOfCharacters - index, text);
538 Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const
540 if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE ||
541 (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged))
543 Window window = DevelWindow::Get(actor);
544 return static_cast<Dali::LayoutDirection::Type>(window ? window.GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>() : LayoutDirection::LEFT_TO_RIGHT);
548 return static_cast<Dali::LayoutDirection::Type>(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
552 Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection()
554 if(mUpdateTextDirection)
556 // Operations that can be done only once until the text changes.
557 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
565 // Set the update info to relayout the whole text.
566 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
567 mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count();
569 // Make sure the model is up-to-date before layouting
570 UpdateModel(onlyOnceOperations);
573 Relayouter::DoRelayout(*this,
574 Size(MAX_FLOAT, MAX_FLOAT),
575 static_cast<OperationsMask>(onlyOnceOperations |
576 LAYOUT | REORDER | UPDATE_DIRECTION),
577 naturalSize.GetVectorXY());
579 // Do not do again the only once operations.
580 mOperationsPending = static_cast<OperationsMask>(mOperationsPending & ~onlyOnceOperations);
582 // Clear the update info. This info will be set the next time the text is updated.
583 mTextUpdateInfo.Clear();
585 // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
586 mTextUpdateInfo.mFullRelayoutNeeded = true;
588 mUpdateTextDirection = false;
591 return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
594 void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters)
596 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
597 mTextUpdateInfo.mStartGlyphIndex = 0u;
598 mTextUpdateInfo.mStartLineIndex = 0u;
599 numberOfCharacters = 0u;
601 const Length numberOfParagraphs = mModel->mLogicalModel->mParagraphInfo.Count();
602 if(0u == numberOfParagraphs)
604 mTextUpdateInfo.mParagraphCharacterIndex = 0u;
605 numberOfCharacters = 0u;
607 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
609 // Nothing else to do if there are no paragraphs.
613 // Find the paragraphs to be updated.
614 Vector<ParagraphRunIndex> paragraphsToBeUpdated;
615 if(mTextUpdateInfo.mCharacterIndex >= mTextUpdateInfo.mPreviousNumberOfCharacters)
617 // Text is being added at the end of the current text.
618 if(mTextUpdateInfo.mIsLastCharacterNewParagraph)
620 // Text is being added in a new paragraph after the last character of the text.
621 mTextUpdateInfo.mParagraphCharacterIndex = mTextUpdateInfo.mPreviousNumberOfCharacters;
622 numberOfCharacters = 0u;
623 mTextUpdateInfo.mRequestedNumberOfCharacters = mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
625 mTextUpdateInfo.mStartGlyphIndex = mModel->mVisualModel->mGlyphs.Count();
626 mTextUpdateInfo.mStartLineIndex = mModel->mVisualModel->mLines.Count() - 1u;
628 // Nothing else to do;
632 paragraphsToBeUpdated.PushBack(numberOfParagraphs - 1u);
636 Length numberOfCharactersToUpdate = 0u;
637 if(mTextUpdateInfo.mFullRelayoutNeeded)
639 numberOfCharactersToUpdate = mTextUpdateInfo.mPreviousNumberOfCharacters;
643 numberOfCharactersToUpdate = (mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) ? mTextUpdateInfo.mNumberOfCharactersToRemove : 1u;
645 mModel->mLogicalModel->FindParagraphs(mTextUpdateInfo.mCharacterIndex,
646 numberOfCharactersToUpdate,
647 paragraphsToBeUpdated);
650 if(0u != paragraphsToBeUpdated.Count())
652 const ParagraphRunIndex firstParagraphIndex = *(paragraphsToBeUpdated.Begin());
653 const ParagraphRun& firstParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + firstParagraphIndex);
654 mTextUpdateInfo.mParagraphCharacterIndex = firstParagraph.characterRun.characterIndex;
656 ParagraphRunIndex lastParagraphIndex = *(paragraphsToBeUpdated.End() - 1u);
657 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex);
659 if((mTextUpdateInfo.mNumberOfCharactersToRemove > 0u) && // Some character are removed.
660 (lastParagraphIndex < numberOfParagraphs - 1u) && // There is a next paragraph.
661 ((lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters) == // The last removed character is the new paragraph character.
662 (mTextUpdateInfo.mCharacterIndex + mTextUpdateInfo.mNumberOfCharactersToRemove)))
664 // The new paragraph character of the last updated paragraph has been removed so is going to be merged with the next one.
665 const ParagraphRun& lastParagraph = *(mModel->mLogicalModel->mParagraphInfo.Begin() + lastParagraphIndex + 1u);
667 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
671 numberOfCharacters = lastParagraph.characterRun.characterIndex + lastParagraph.characterRun.numberOfCharacters - mTextUpdateInfo.mParagraphCharacterIndex;
675 mTextUpdateInfo.mRequestedNumberOfCharacters = numberOfCharacters + mTextUpdateInfo.mNumberOfCharactersToAdd - mTextUpdateInfo.mNumberOfCharactersToRemove;
676 mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex);
679 void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations)
681 ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations);
684 bool Controller::Impl::UpdateModel(OperationsMask operationsRequired)
686 return ControllerImplModelUpdater::Update(*this, operationsRequired);
689 void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle)
691 SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor);
694 float Controller::Impl::GetDefaultFontLineHeight()
696 FontId defaultFontId = 0u;
697 if(nullptr == mFontDefaults)
699 TextAbstraction::FontDescription fontDescription;
700 defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * GetFontSizeScale());
704 defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * GetFontSizeScale());
707 Text::FontMetrics fontMetrics;
708 mMetrics->GetFontMetrics(defaultFontId, fontMetrics);
710 return (fontMetrics.ascender - fontMetrics.descender);
713 bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing)
715 if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000)
717 mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
719 RelayoutAllCharacters();
725 bool Controller::Impl::SetDefaultLineSize(float lineSize)
727 if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000)
729 mLayoutEngine.SetDefaultLineSize(lineSize);
731 RelayoutAllCharacters();
737 bool Controller::Impl::SetRelativeLineSize(float relativeLineSize)
739 if(std::fabs(relativeLineSize - GetRelativeLineSize()) > Math::MACHINE_EPSILON_1000)
741 mLayoutEngine.SetRelativeLineSize(relativeLineSize);
743 RelayoutAllCharacters();
749 float Controller::Impl::GetRelativeLineSize()
751 return mLayoutEngine.GetRelativeLineSize();
754 string Controller::Impl::GetSelectedText()
757 if(EventData::SELECTING == mEventData->mState)
759 RetrieveSelection(text, false);
764 string Controller::Impl::CopyText()
767 RetrieveSelection(text, false);
768 SendSelectionToClipboard(false); // Text not modified
770 mEventData->mUpdateCursorPosition = true;
772 RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
777 string Controller::Impl::CutText()
780 RetrieveSelection(text, false);
787 SendSelectionToClipboard(true); // Synchronous call to modify text
788 mOperationsPending = ALL_OPERATIONS;
790 if((0u != mModel->mLogicalModel->mText.Count()) ||
791 !IsPlaceholderAvailable())
793 QueueModifyEvent(ModifyEvent::TEXT_DELETED);
797 PlaceholderHandler::ShowPlaceholderText(*this);
800 mEventData->mUpdateCursorPosition = true;
801 mEventData->mScrollAfterDelete = true;
805 if(nullptr != mEditableControlInterface)
807 mEditableControlInterface->TextChanged(true);
812 void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd)
814 if(nullptr == mEventData)
816 // Nothing to do if there is no text.
820 if(mEventData->mSelectionEnabled && (pStart || pEnd))
822 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
823 uint32_t oldStart = mEventData->mLeftSelectionPosition;
824 uint32_t oldEnd = mEventData->mRightSelectionPosition;
828 mEventData->mLeftSelectionPosition = std::min(*pStart, length);
832 mEventData->mRightSelectionPosition = std::min(*pEnd, length);
835 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
837 ChangeState(EventData::EDITING);
838 mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition;
839 mEventData->mUpdateCursorPosition = true;
843 ChangeState(EventData::SELECTING);
844 mEventData->mUpdateHighlightBox = true;
845 mEventData->mUpdateLeftSelectionPosition = true;
846 mEventData->mUpdateRightSelectionPosition = true;
849 if(mSelectableControlInterface != nullptr)
851 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
856 CharacterIndex Controller::Impl::GetPrimaryCursorPosition() const
858 if(nullptr == mEventData)
862 return mEventData->mPrimaryCursorPosition;
865 bool Controller::Impl::SetPrimaryCursorPosition(CharacterIndex index, bool focused)
867 if(nullptr == mEventData)
869 // Nothing to do if there is no text.
873 if(mEventData->mPrimaryCursorPosition == index && mEventData->mState != EventData::SELECTING)
875 // Nothing for same cursor position.
879 uint32_t length = static_cast<uint32_t>(mModel->mLogicalModel->mText.Count());
880 uint32_t oldCursorPos = mEventData->mPrimaryCursorPosition;
881 mEventData->mPrimaryCursorPosition = std::min(index, length);
882 // If there is no focus, only the value is updated.
885 bool wasInSelectingState = mEventData->mState == EventData::SELECTING;
886 uint32_t oldStart = mEventData->mLeftSelectionPosition;
887 uint32_t oldEnd = mEventData->mRightSelectionPosition;
888 ChangeState(EventData::EDITING);
889 mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
890 mEventData->mUpdateCursorPosition = true;
892 if(mSelectableControlInterface != nullptr && wasInSelectingState)
894 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
897 ScrollTextToMatchCursor();
900 if(nullptr != mEditableControlInterface)
902 mEditableControlInterface->CursorPositionChanged(oldCursorPos, mEventData->mPrimaryCursorPosition);
908 Uint32Pair Controller::Impl::GetTextSelectionRange() const
914 range.first = mEventData->mLeftSelectionPosition;
915 range.second = mEventData->mRightSelectionPosition;
921 bool Controller::Impl::IsEditable() const
923 return mEventData && mEventData->mEditingEnabled;
926 void Controller::Impl::SetEditable(bool editable)
930 mEventData->mEditingEnabled = editable;
932 if(mEventData->mDecorator)
934 bool decoratorEditable = editable && mIsUserInteractionEnabled;
935 mEventData->mDecorator->SetEditable(decoratorEditable);
940 void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont)
942 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n");
944 if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes
946 DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str());
947 mFontDefaults->mFontDescription.family = newDefaultFont;
955 void Controller::Impl::RetrieveSelection(std::string& selectedText, bool deleteAfterRetrieval)
957 if(mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition)
959 // Nothing to select if handles are in the same place.
960 selectedText.clear();
964 const bool handlesCrossed = mEventData->mLeftSelectionPosition > mEventData->mRightSelectionPosition;
966 //Get start and end position of selection
967 const CharacterIndex startOfSelectedText = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
968 const Length lengthOfSelectedText = (handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition) - startOfSelectedText;
970 Vector<Character>& utf32Characters = mModel->mLogicalModel->mText;
971 const Length numberOfCharacters = utf32Characters.Count();
973 // Validate the start and end selection points
974 if((startOfSelectedText + lengthOfSelectedText) <= numberOfCharacters)
976 //Get text as a UTF8 string
977 Utf32ToUtf8(&utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText);
979 if(deleteAfterRetrieval) // Only delete text if copied successfully
981 // Keep a copy of the current input style.
982 InputStyle currentInputStyle;
983 currentInputStyle.Copy(mEventData->mInputStyle);
985 // Set as input style the style of the first deleted character.
986 mModel->mLogicalModel->RetrieveStyle(startOfSelectedText, mEventData->mInputStyle);
988 // Compare if the input style has changed.
989 const bool hasInputStyleChanged = !currentInputStyle.Equal(mEventData->mInputStyle);
991 if(hasInputStyleChanged)
993 const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(mEventData->mInputStyle);
994 // Queue the input style changed signal.
995 mEventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
998 mModel->mLogicalModel->UpdateTextStyleRuns(startOfSelectedText, -static_cast<int>(lengthOfSelectedText));
1000 // Mark the paragraphs to be updated.
1001 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1003 mTextUpdateInfo.mCharacterIndex = 0;
1004 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1005 mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText;
1006 mTextUpdateInfo.mClearAll = true;
1010 mTextUpdateInfo.mCharacterIndex = startOfSelectedText;
1011 mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText;
1014 // Delete text between handles
1015 Vector<Character>::Iterator first = utf32Characters.Begin() + startOfSelectedText;
1016 Vector<Character>::Iterator last = first + lengthOfSelectedText;
1017 utf32Characters.Erase(first, last);
1019 // Will show the cursor at the first character of the selection.
1020 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mRightSelectionPosition : mEventData->mLeftSelectionPosition;
1024 // Will show the cursor at the last character of the selection.
1025 mEventData->mPrimaryCursorPosition = handlesCrossed ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1028 mEventData->mDecoratorUpdated = true;
1032 void Controller::Impl::SetSelection(int start, int end)
1034 uint32_t oldStart = mEventData->mLeftSelectionPosition;
1035 uint32_t oldEnd = mEventData->mRightSelectionPosition;
1037 mEventData->mLeftSelectionPosition = start;
1038 mEventData->mRightSelectionPosition = end;
1039 mEventData->mUpdateCursorPosition = true;
1041 if(mSelectableControlInterface != nullptr)
1043 mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
1047 std::pair<int, int> Controller::Impl::GetSelectionIndexes() const
1049 return {mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition};
1052 void Controller::Impl::ShowClipboard()
1054 if(EnsureClipboardCreated())
1056 mClipboard.ShowClipboard();
1060 void Controller::Impl::HideClipboard()
1062 if(EnsureClipboardCreated() && mClipboardHideEnabled)
1064 mClipboard.HideClipboard();
1068 void Controller::Impl::SetClipboardHideEnable(bool enable)
1070 mClipboardHideEnabled = enable;
1073 bool Controller::Impl::CopyStringToClipboard(const std::string& source)
1075 //Send string to clipboard
1076 return (EnsureClipboardCreated() && mClipboard.SetItem(source));
1079 void Controller::Impl::SendSelectionToClipboard(bool deleteAfterSending)
1081 std::string selectedText;
1082 RetrieveSelection(selectedText, deleteAfterSending);
1083 CopyStringToClipboard(selectedText);
1084 ChangeState(EventData::EDITING);
1087 void Controller::Impl::RequestGetTextFromClipboard()
1089 if(EnsureClipboardCreated())
1091 mClipboard.RequestItem();
1095 void Controller::Impl::RepositionSelectionHandles()
1097 SelectionHandleController::Reposition(*this);
1099 void Controller::Impl::RepositionSelectionHandles(float visualX, float visualY, Controller::NoTextTap::Action action)
1101 SelectionHandleController::Reposition(*this, visualX, visualY, action);
1104 void Controller::Impl::SetPopupButtons()
1107 * Sets the Popup buttons to be shown depending on State.
1109 * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
1111 * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
1114 bool isEditable = IsEditable();
1115 TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
1117 if(EventData::SELECTING == mEventData->mState)
1119 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY);
1122 buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT);
1125 if(!IsClipboardEmpty())
1129 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1131 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1134 if(!mEventData->mAllTextSelected)
1136 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL));
1139 else if(EventData::EDITING_WITH_POPUP == mEventData->mState)
1141 if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
1143 buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL);
1146 if(!IsClipboardEmpty())
1150 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1152 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1155 else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState)
1157 if(!IsClipboardEmpty())
1161 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE));
1163 buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD));
1167 mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow);
1170 void Controller::Impl::ChangeState(EventData::State newState)
1172 ChangeTextControllerState(*this, newState);
1175 void Controller::Impl::GetCursorPosition(CharacterIndex logical,
1176 CursorInfo& cursorInfo)
1178 if(!IsShowingRealText())
1180 // Do not want to use the place-holder text to set the cursor position.
1182 // Use the line's height of the font's family set to set the cursor's size.
1183 // If there is no font's family set, use the default font.
1184 // Use the current alignment to place the cursor at the beginning, center or end of the box.
1186 cursorInfo.lineOffset = 0.f;
1187 cursorInfo.lineHeight = GetDefaultFontLineHeight();
1188 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
1191 if(mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS)
1193 isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1196 switch(mModel->mHorizontalAlignment)
1198 case Text::HorizontalAlignment::BEGIN:
1202 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1206 cursorInfo.primaryPosition.x = 0.f;
1210 case Text::HorizontalAlignment::CENTER:
1212 cursorInfo.primaryPosition.x = floorf(0.5f * mModel->mVisualModel->mControlSize.width);
1215 case Text::HorizontalAlignment::END:
1219 cursorInfo.primaryPosition.x = 0.f;
1223 cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth();
1229 // Nothing else to do.
1233 const bool isMultiLine = (Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout());
1234 GetCursorPositionParameters parameters;
1235 parameters.visualModel = mModel->mVisualModel;
1236 parameters.logicalModel = mModel->mLogicalModel;
1237 parameters.metrics = mMetrics;
1238 parameters.logical = logical;
1239 parameters.isMultiline = isMultiLine;
1241 float defaultFontLineHeight = GetDefaultFontLineHeight();
1243 Text::GetCursorPosition(parameters,
1244 defaultFontLineHeight,
1247 // Adds Outline offset.
1248 const float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1249 cursorInfo.primaryPosition.x += outlineWidth;
1250 cursorInfo.primaryPosition.y += outlineWidth;
1251 cursorInfo.secondaryPosition.x += outlineWidth;
1252 cursorInfo.secondaryPosition.y += outlineWidth;
1256 // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control.
1258 // Note the white spaces laid-out at the end of the line might exceed the boundaries of the control.
1259 // The reason is a wrapped line must not start with a white space so they are laid-out at the end of the line.
1261 if(0.f > cursorInfo.primaryPosition.x)
1263 cursorInfo.primaryPosition.x = 0.f;
1266 const float edgeWidth = mModel->mVisualModel->mControlSize.width - static_cast<float>(mEventData->mDecorator->GetCursorWidth());
1267 if(cursorInfo.primaryPosition.x > edgeWidth)
1269 cursorInfo.primaryPosition.x = edgeWidth;
1274 CharacterIndex Controller::Impl::CalculateNewCursorIndex(CharacterIndex index) const
1276 if(nullptr == mEventData)
1278 // Nothing to do if there is no text input.
1282 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1284 const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin();
1285 const Length* const charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin();
1287 GlyphIndex glyphIndex = *(charactersToGlyphBuffer + index);
1288 Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1290 if(numberOfCharacters > 1u)
1292 const Script script = mModel->mLogicalModel->GetScript(index);
1293 if(HasLigatureMustBreak(script))
1295 // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ...
1296 numberOfCharacters = 1u;
1301 while(0u == numberOfCharacters)
1304 numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex);
1308 if(index < mEventData->mPrimaryCursorPosition)
1310 cursorIndex -= numberOfCharacters;
1314 cursorIndex += numberOfCharacters;
1317 // Will update the cursor hook position.
1318 mEventData->mUpdateCursorHookPosition = true;
1323 void Controller::Impl::UpdateCursorPosition(const CursorInfo& cursorInfo)
1325 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this);
1326 if(nullptr == mEventData)
1328 // Nothing to do if there is no text input.
1329 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n");
1333 const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition;
1335 mEventData->mDecorator->SetGlyphOffset(PRIMARY_CURSOR, cursorInfo.glyphOffset);
1337 // Sets the cursor position.
1338 mEventData->mDecorator->SetPosition(PRIMARY_CURSOR,
1341 cursorInfo.primaryCursorHeight,
1342 cursorInfo.lineHeight);
1343 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y);
1345 if(mEventData->mUpdateGrabHandlePosition)
1347 // Sets the grab handle position.
1348 mEventData->mDecorator->SetPosition(GRAB_HANDLE,
1350 cursorInfo.lineOffset + mModel->mScrollPosition.y,
1351 cursorInfo.lineHeight);
1354 if(cursorInfo.isSecondaryCursor)
1356 mEventData->mDecorator->SetPosition(SECONDARY_CURSOR,
1357 cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x,
1358 cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y,
1359 cursorInfo.secondaryCursorHeight,
1360 cursorInfo.lineHeight);
1361 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + mModel->mScrollPosition.x, cursorInfo.secondaryPosition.y + mModel->mScrollPosition.y);
1364 // Set which cursors are active according the state.
1365 if(EventData::IsEditingState(mEventData->mState) || (EventData::GRAB_HANDLE_PANNING == mEventData->mState))
1367 if(cursorInfo.isSecondaryCursor)
1369 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_BOTH);
1373 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY);
1378 mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE);
1381 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n");
1384 void Controller::Impl::UpdateSelectionHandle(HandleType handleType,
1385 const CursorInfo& cursorInfo)
1387 SelectionHandleController::Update(*this, handleType, cursorInfo);
1390 void Controller::Impl::ClampHorizontalScroll(const Vector2& layoutSize)
1392 // Clamp between -space & -alignment offset.
1394 if(layoutSize.width > mModel->mVisualModel->mControlSize.width)
1396 const float space = (layoutSize.width - mModel->mVisualModel->mControlSize.width) + mModel->mAlignmentOffset;
1397 mModel->mScrollPosition.x = (mModel->mScrollPosition.x < -space) ? -space : mModel->mScrollPosition.x;
1398 mModel->mScrollPosition.x = (mModel->mScrollPosition.x > -mModel->mAlignmentOffset) ? -mModel->mAlignmentOffset : mModel->mScrollPosition.x;
1400 mEventData->mDecoratorUpdated = true;
1404 mModel->mScrollPosition.x = 0.f;
1408 void Controller::Impl::ClampVerticalScroll(const Vector2& layoutSize)
1410 if(Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout())
1412 // Nothing to do if the text is single line.
1416 // Clamp between -space & 0.
1417 if(layoutSize.height > mModel->mVisualModel->mControlSize.height)
1419 const float space = (layoutSize.height - mModel->mVisualModel->mControlSize.height);
1420 mModel->mScrollPosition.y = (mModel->mScrollPosition.y < -space) ? -space : mModel->mScrollPosition.y;
1421 mModel->mScrollPosition.y = (mModel->mScrollPosition.y > 0.f) ? 0.f : mModel->mScrollPosition.y;
1423 mEventData->mDecoratorUpdated = true;
1427 mModel->mScrollPosition.y = 0.f;
1431 void Controller::Impl::ScrollToMakePositionVisible(const Vector2& position, float lineHeight)
1433 const float cursorWidth = mEventData->mDecorator ? static_cast<float>(mEventData->mDecorator->GetCursorWidth()) : 0.f;
1435 // position is in actor's coords.
1436 const float positionEndX = position.x + cursorWidth;
1437 const float positionEndY = position.y + lineHeight;
1439 // Transform the position to decorator coords.
1440 const float decoratorPositionBeginX = position.x + mModel->mScrollPosition.x;
1441 const float decoratorPositionEndX = positionEndX + mModel->mScrollPosition.x;
1443 const float decoratorPositionBeginY = position.y + mModel->mScrollPosition.y;
1444 const float decoratorPositionEndY = positionEndY + mModel->mScrollPosition.y;
1446 if(decoratorPositionBeginX < 0.f)
1448 mModel->mScrollPosition.x = -position.x;
1450 else if(decoratorPositionEndX > mModel->mVisualModel->mControlSize.width)
1452 mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX;
1455 if(Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout())
1457 if(decoratorPositionBeginY < 0.f)
1459 mModel->mScrollPosition.y = -position.y;
1461 else if(decoratorPositionEndY > mModel->mVisualModel->mControlSize.height)
1463 mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY;
1465 else if(mModel->mLogicalModel->mText.Count() == 0u)
1467 Relayouter::CalculateVerticalOffset(*this, mModel->mVisualModel->mControlSize);
1472 void Controller::Impl::ScrollTextToMatchCursor(const CursorInfo& cursorInfo)
1474 // Get the current cursor position in decorator coords.
1475 const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1477 const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter(mEventData->mPrimaryCursorPosition);
1479 // Calculate the offset to match the cursor position before the character was deleted.
1480 mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x;
1482 //If text control has more than two lines and current line index is not last, calculate scrollpositionY
1483 if(mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() - 1u)
1485 const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset(PRIMARY_CURSOR);
1486 mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset;
1489 ClampHorizontalScroll(mModel->mVisualModel->GetLayoutSize());
1490 ClampVerticalScroll(mModel->mVisualModel->GetLayoutSize());
1492 // Makes the new cursor position visible if needed.
1493 ScrollToMakePositionVisible(cursorInfo.primaryPosition, cursorInfo.lineHeight);
1496 void Controller::Impl::ScrollTextToMatchCursor()
1498 CursorInfo cursorInfo;
1499 GetCursorPosition(mEventData->mPrimaryCursorPosition, cursorInfo);
1500 ScrollTextToMatchCursor(cursorInfo);
1503 void Controller::Impl::RequestRelayout()
1505 if(nullptr != mControlInterface)
1507 mControlInterface->RequestTextRelayout();
1511 void Controller::Impl::RelayoutAllCharacters()
1513 // relayout all characters
1514 mTextUpdateInfo.mCharacterIndex = 0;
1515 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1516 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1517 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | LAYOUT);
1519 mTextUpdateInfo.mFullRelayoutNeeded = true;
1521 // Need to recalculate natural size
1522 mRecalculateNaturalSize = true;
1525 if((mEventData != nullptr) && (mEventData->mState == EventData::SELECTING))
1527 ChangeState(EventData::EDITING);
1533 bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty()
1535 return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count());
1538 void Controller::Impl::ProcessInputStyleChangedSignals()
1542 if(mEditableControlInterface)
1544 // Emit the input style changed signal for each mask
1545 std::for_each(mEventData->mInputStyleChangedQueue.begin(),
1546 mEventData->mInputStyleChangedQueue.end(),
1547 [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); });
1550 mEventData->mInputStyleChangedQueue.Clear();
1554 void Controller::Impl::ScrollBy(Vector2 scroll)
1556 if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0))
1558 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1559 const Vector2 currentScroll = mModel->mScrollPosition;
1561 scroll.x = -scroll.x;
1562 scroll.y = -scroll.y;
1564 if(fabs(scroll.x) > Math::MACHINE_EPSILON_0)
1566 mModel->mScrollPosition.x += scroll.x;
1567 ClampHorizontalScroll(layoutSize);
1570 if(fabs(scroll.y) > Math::MACHINE_EPSILON_0)
1572 mModel->mScrollPosition.y += scroll.y;
1573 ClampVerticalScroll(layoutSize);
1576 if(mModel->mScrollPosition != currentScroll)
1578 mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll);
1584 bool Controller::Impl::IsScrollable(const Vector2& displacement)
1586 bool isScrollable = false;
1589 const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
1590 const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
1591 if(isHorizontalScrollEnabled ||isVerticalScrollEnabled)
1593 const Vector2& targetSize = mModel->mVisualModel->mControlSize;
1594 const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
1595 const Vector2& scrollPosition = mModel->mScrollPosition;
1597 if(isHorizontalScrollEnabled)
1599 const float displacementX = displacement.x;
1600 const float positionX = scrollPosition.x + displacementX;
1601 if(layoutSize.width > targetSize.width && -positionX > 0.f && -positionX < layoutSize.width - targetSize.width)
1603 isScrollable = true;
1607 if(isVerticalScrollEnabled)
1609 const float displacementY = displacement.y;
1610 const float positionY = scrollPosition.y + displacementY;
1611 if(layoutSize.height > targetSize.height && -positionY > 0 && -positionY < layoutSize.height - targetSize.height)
1613 isScrollable = true;
1618 return isScrollable;
1621 float Controller::Impl::GetHorizontalScrollPosition()
1623 // Scroll values are negative internally so we convert them to positive numbers
1624 return mEventData ? -mModel->mScrollPosition.x : 0.0f;
1627 float Controller::Impl::GetVerticalScrollPosition()
1629 // Scroll values are negative internally so we convert them to positive numbers
1630 return mEventData ? -mModel->mScrollPosition.y : 0.0f;
1633 Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const
1636 return Vector3(10.f, 10.f, 10.f);
1639 Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const
1642 return Vector2(10.f, 10.f);
1645 Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor)
1647 auto actor = Toolkit::TextAnchor::New();
1648 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
1649 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
1650 const Vector3 anchorPosition = GetAnchorPosition(anchor);
1651 actor.SetProperty(Actor::Property::POSITION, anchorPosition);
1652 const Vector2 anchorSize = GetAnchorSize(anchor);
1653 actor.SetProperty(Actor::Property::SIZE, anchorSize);
1654 std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex);
1655 actor.SetProperty(Actor::Property::NAME, anchorText);
1656 actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href));
1657 actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast<int>(anchor.startIndex));
1658 actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast<int>(anchor.endIndex));
1662 void Controller::Impl::GetAnchorActors(std::vector<Toolkit::TextAnchor>& anchorActors)
1664 /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character,
1665 we need to create and destroy potentially many actors. Some optimization can be considered here.
1666 Maybe a "dirty" flag in mLogicalModel? */
1667 anchorActors.clear();
1668 for(auto& anchor : mModel->mLogicalModel->mAnchors)
1670 auto actor = CreateAnchorActor(anchor);
1671 anchorActors.push_back(actor);
1675 int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const
1677 Vector<Anchor>::Iterator it = mModel->mLogicalModel->mAnchors.Begin();
1679 while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset))
1684 return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin();
1687 void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns)
1689 //Underlined character runs for markup-processor
1690 const Vector<UnderlinedCharacterRun>& underlinedCharacterRuns = mModel->mLogicalModel->mUnderlinedCharacterRuns;
1691 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1692 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1694 if(shouldClearPreUnderlineRuns)
1696 mModel->mVisualModel->mUnderlineRuns.Clear();
1699 for(Vector<UnderlinedCharacterRun>::ConstIterator it = underlinedCharacterRuns.Begin(), endIt = underlinedCharacterRuns.End(); it != endIt; ++it)
1701 CharacterIndex characterIndex = it->characterRun.characterIndex;
1702 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1704 if(numberOfCharacters == 0)
1709 // Create one run for all glyphs of all run's characters that has same properties
1710 // This enhance performance and reduce the needed memory to store glyphs-runs
1711 UnderlinedGlyphRun underlineGlyphRun;
1712 underlineGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1713 underlineGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1714 //Copy properties (attributes)
1715 underlineGlyphRun.properties = it->properties;
1717 for(Length index = 1u; index < numberOfCharacters; index++)
1719 underlineGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1722 mModel->mVisualModel->mUnderlineRuns.PushBack(underlineGlyphRun);
1725 // Reset flag. The updates have been applied from logical to visual.
1726 mModel->mLogicalModel->mUnderlineRunsUpdated = false;
1729 void Controller::Impl::CopyStrikethroughFromLogicalToVisualModels()
1731 //Strikethrough character runs from markup-processor
1732 const Vector<StrikethroughCharacterRun>& strikethroughCharacterRuns = mModel->mLogicalModel->mStrikethroughCharacterRuns;
1733 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1734 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1736 mModel->mVisualModel->mStrikethroughRuns.Clear();
1738 for(Vector<StrikethroughCharacterRun>::ConstIterator it = strikethroughCharacterRuns.Begin(), endIt = strikethroughCharacterRuns.End(); it != endIt; ++it)
1740 CharacterIndex characterIndex = it->characterRun.characterIndex;
1741 Length numberOfCharacters = it->characterRun.numberOfCharacters;
1743 if(numberOfCharacters == 0)
1748 StrikethroughGlyphRun strikethroughGlyphRun;
1749 strikethroughGlyphRun.properties = it->properties;
1750 strikethroughGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1751 strikethroughGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1753 for(Length index = 1u; index < numberOfCharacters; index++)
1755 strikethroughGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1758 mModel->mVisualModel->mStrikethroughRuns.PushBack(strikethroughGlyphRun);
1761 // Reset flag. The updates have been applied from logical to visual.
1762 mModel->mLogicalModel->mStrikethroughRunsUpdated = false;
1765 void Controller::Impl::CopyCharacterSpacingFromLogicalToVisualModels()
1767 //CharacterSpacing character runs from markup-processor
1768 const Vector<CharacterSpacingCharacterRun>& characterSpacingCharacterRuns = mModel->mLogicalModel->mCharacterSpacingCharacterRuns;
1769 const Vector<GlyphIndex>& charactersToGlyph = mModel->mVisualModel->mCharactersToGlyph;
1770 const Vector<Length>& glyphsPerCharacter = mModel->mVisualModel->mGlyphsPerCharacter;
1772 mModel->mVisualModel->mCharacterSpacingRuns.Clear();
1774 for(Vector<CharacterSpacingCharacterRun>::ConstIterator it = characterSpacingCharacterRuns.Begin(), endIt = characterSpacingCharacterRuns.End(); it != endIt; ++it)
1776 const CharacterIndex& characterIndex = it->characterRun.characterIndex;
1777 const Length& numberOfCharacters = it->characterRun.numberOfCharacters;
1779 if(numberOfCharacters == 0)
1784 CharacterSpacingGlyphRun characterSpacingGlyphRun;
1785 characterSpacingGlyphRun.value = it->value;
1786 characterSpacingGlyphRun.glyphRun.glyphIndex = charactersToGlyph[characterIndex];
1787 characterSpacingGlyphRun.glyphRun.numberOfGlyphs = glyphsPerCharacter[characterIndex];
1789 for(Length index = 1u; index < numberOfCharacters; index++)
1791 characterSpacingGlyphRun.glyphRun.numberOfGlyphs += glyphsPerCharacter[characterIndex + index];
1794 mModel->mVisualModel->mCharacterSpacingRuns.PushBack(characterSpacingGlyphRun);
1796 mModel->mLogicalModel->mCharacterSpacingRunsUpdated = false;
1799 void Controller::Impl::SetAutoScrollEnabled(bool enable)
1801 if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX)
1803 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1806 UPDATE_LAYOUT_SIZE |
1811 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n");
1812 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | UPDATE_DIRECTION);
1816 DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n");
1819 mIsAutoScrollEnabled = enable;
1824 DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n");
1825 mIsAutoScrollEnabled = false;
1829 void Controller::Impl::SetEnableCursorBlink(bool enable)
1831 DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled");
1835 mEventData->mCursorBlinkEnabled = enable;
1837 if(!enable && mEventData->mDecorator)
1839 mEventData->mDecorator->StopCursorBlink();
1844 void Controller::Impl::SetMultiLineEnabled(bool enable)
1846 const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX;
1848 if(layout != mLayoutEngine.GetLayout())
1850 // Set the layout type.
1851 mLayoutEngine.SetLayout(layout);
1853 // Set the flags to redo the layout operations
1854 const OperationsMask layoutOperations = static_cast<OperationsMask>(LAYOUT |
1855 UPDATE_LAYOUT_SIZE |
1859 mTextUpdateInfo.mFullRelayoutNeeded = true;
1860 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | layoutOperations);
1862 // Need to recalculate natural size
1863 mRecalculateNaturalSize = true;
1869 void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment)
1871 if(alignment != mModel->mHorizontalAlignment)
1873 // Set the alignment.
1874 mModel->mHorizontalAlignment = alignment;
1875 UpdateCursorPositionForAlignment(*this, true);
1880 void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment)
1882 if(alignment != mModel->mVerticalAlignment)
1884 // Set the alignment.
1885 mModel->mVerticalAlignment = alignment;
1886 UpdateCursorPositionForAlignment(*this, false);
1891 void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode)
1893 if(lineWrapMode != mModel->mLineWrapMode)
1895 // Update Text layout for applying wrap mode
1896 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1899 UPDATE_LAYOUT_SIZE |
1902 if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
1903 (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break
1905 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | GET_LINE_BREAKS);
1908 // Set the text wrap mode.
1909 mModel->mLineWrapMode = lineWrapMode;
1911 mTextUpdateInfo.mCharacterIndex = 0u;
1912 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1913 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1920 void Controller::Impl::SetDefaultColor(const Vector4& color)
1924 if(!IsShowingPlaceholderText())
1926 mModel->mVisualModel->SetTextColor(color);
1927 mOperationsPending = static_cast<OperationsMask>(mOperationsPending | COLOR);
1932 void Controller::Impl::SetUserInteractionEnabled(bool enabled)
1934 mIsUserInteractionEnabled = enabled;
1936 if(mEventData && mEventData->mDecorator)
1938 bool editable = mEventData->mEditingEnabled && enabled;
1939 mEventData->mDecorator->SetEditable(editable);
1943 void Controller::Impl::ClearFontData()
1947 mFontDefaults->mFontId = 0u; // Remove old font ID
1950 // Set flags to update the model.
1951 mTextUpdateInfo.mCharacterIndex = 0u;
1952 mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters;
1953 mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count();
1955 mTextUpdateInfo.mClearAll = true;
1956 mTextUpdateInfo.mFullRelayoutNeeded = true;
1957 mRecalculateNaturalSize = true;
1959 mOperationsPending = static_cast<OperationsMask>(mOperationsPending |
1965 UPDATE_LAYOUT_SIZE |
1970 void Controller::Impl::ClearStyleData()
1972 mModel->mLogicalModel->mColorRuns.Clear();
1973 mModel->mLogicalModel->ClearFontDescriptionRuns();
1974 mModel->mLogicalModel->ClearStrikethroughRuns();
1977 void Controller::Impl::ResetScrollPosition()
1981 // Reset the scroll position.
1982 mModel->mScrollPosition = Vector2::ZERO;
1983 mEventData->mScrollAfterUpdatePosition = true;
1987 } // namespace Dali::Toolkit::Text