X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=35ad9593a8a0ffc6b715b8dce3944fcb00abb19e;hp=2edca46cecb23d01f20b97e74b821d546a46bcef;hb=7c13cc0c065ae22e7ad0deaea4f56730ff050c5c;hpb=2a1d671b3526a30bfb8698a8bde4496b82be7f35 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 2edca46..35ad959 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -19,17 +19,20 @@ #include // EXTERNAL INCLUDES +#include #include -#include +#include +#include // INTERNAL INCLUDES -#include -#include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -43,17 +46,9 @@ namespace Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); #endif -struct BackgroundVertex -{ - Vector2 mPosition; ///< Vertex posiiton - Vector4 mColor; ///< Vertex color -}; +constexpr float MAX_FLOAT = std::numeric_limits::max(); -struct BackgroundMesh -{ - Vector mVertices; ///< container of vertices - Vector mIndices; ///< container of indices -}; +const std::string EMPTY_STRING(""); } // namespace @@ -129,6 +124,243 @@ void SetDefaultInputStyle(InputStyle& inputStyle, const FontDefaults* const font } } } + +void ChangeTextControllerState(Controller::Impl& impl, EventData::State newState) +{ + EventData* eventData = impl.mEventData; + + if(nullptr == eventData) + { + // Nothing to do if there is no text input. + return; + } + + DecoratorPtr& decorator = eventData->mDecorator; + if(!decorator) + { + // Nothing to do if there is no decorator. + return; + } + + DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", eventData->mState, newState); + + if(eventData->mState != newState) + { + eventData->mPreviousState = eventData->mState; + eventData->mState = newState; + + switch(eventData->mState) + { + case EventData::INACTIVE: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + decorator->SetPopupActive(false); + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::INTERRUPTED: + { + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + decorator->SetPopupActive(false); + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::SELECTING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); + } + decorator->SetHighlightActive(true); + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::EDITING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + // Grab handle is not shown until a tap is received whilst EDITING + decorator->SetHandleActive(GRAB_HANDLE, false); + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + case EventData::EDITING_WITH_POPUP: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + if(eventData->mSelectionEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + } + else if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + case EventData::EDITING_WITH_GRAB_HANDLE: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + // Grab handle is not shown until a tap is received whilst EDITING + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::SELECTION_HANDLE_PANNING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); + } + decorator->SetHighlightActive(true); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::GRAB_HANDLE_PANNING: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::EDITING_WITH_PASTE_POPUP: + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState); + + decorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); + if(eventData->mCursorBlinkEnabled) + { + decorator->StartCursorBlink(); + } + + if(eventData->mGrabHandleEnabled) + { + decorator->SetHandleActive(GRAB_HANDLE, true); + } + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(false); + + if(eventData->mGrabHandlePopupEnabled) + { + impl.SetPopupButtons(); + decorator->SetPopupActive(true); + } + eventData->mDecoratorUpdated = true; + break; + } + + case EventData::TEXT_PANNING: + { + decorator->SetActiveCursor(ACTIVE_CURSOR_NONE); + decorator->StopCursorBlink(); + decorator->SetHandleActive(GRAB_HANDLE, false); + if(eventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) || + decorator->IsHandleActive(RIGHT_SELECTION_HANDLE)) + { + decorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); + decorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); + decorator->SetHighlightActive(true); + } + + if(eventData->mGrabHandlePopupEnabled) + { + decorator->SetPopupActive(false); + } + + eventData->mDecoratorUpdated = true; + break; + } + } + } +} + } // unnamed Namespace EventData::EventData(DecoratorPtr decorator, InputMethodContext& inputMethodContext) @@ -256,6 +488,19 @@ Length Controller::Impl::GetNumberOfWhiteSpaces(CharacterIndex index) const return numberOfWhiteSpaces; } +void Controller::Impl::GetText(std::string& text) const +{ + if(!IsShowingPlaceholderText()) + { + // Retrieves the text string. + GetText(0u, text); + } + else + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::GetText %p empty (but showing placeholder)\n", this); + } +} + void Controller::Impl::GetText(CharacterIndex index, std::string& text) const { // Get the total number of characters. @@ -268,6 +513,61 @@ void Controller::Impl::GetText(CharacterIndex index, std::string& text) const } } +Dali::LayoutDirection::Type Controller::Impl::GetLayoutDirection(Dali::Actor& actor) const +{ + if(mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::LOCALE || + (mModel->mMatchLayoutDirection == DevelText::MatchLayoutDirection::INHERIT && !mIsLayoutDirectionChanged)) + { + return static_cast(DevelWindow::Get(actor).GetRootLayer().GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); + } + else + { + return static_cast(actor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get()); + } +} + +Toolkit::DevelText::TextDirection::Type Controller::Impl::GetTextDirection() +{ + if(mUpdateTextDirection) + { + // Operations that can be done only once until the text changes. + const OperationsMask onlyOnceOperations = static_cast(CONVERT_TO_UTF32 | + GET_SCRIPTS | + VALIDATE_FONTS | + GET_LINE_BREAKS | + BIDI_INFO | + SHAPE_TEXT | + GET_GLYPH_METRICS); + + // Set the update info to relayout the whole text. + mTextUpdateInfo.mParagraphCharacterIndex = 0u; + mTextUpdateInfo.mRequestedNumberOfCharacters = mModel->mLogicalModel->mText.Count(); + + // Make sure the model is up-to-date before layouting + UpdateModel(onlyOnceOperations); + + Vector3 naturalSize; + Relayouter::DoRelayout(*this, + Size(MAX_FLOAT, MAX_FLOAT), + static_cast(onlyOnceOperations | + LAYOUT | REORDER | UPDATE_DIRECTION), + naturalSize.GetVectorXY()); + + // Do not do again the only once operations. + mOperationsPending = static_cast(mOperationsPending & ~onlyOnceOperations); + + // Clear the update info. This info will be set the next time the text is updated. + mTextUpdateInfo.Clear(); + + // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT. + mTextUpdateInfo.mFullRelayoutNeeded = true; + + mUpdateTextDirection = false; + } + + return mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT; +} + void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters) { mTextUpdateInfo.mParagraphCharacterIndex = 0u; @@ -353,315 +653,122 @@ void Controller::Impl::CalculateTextUpdateIndices(Length& numberOfCharacters) mTextUpdateInfo.mStartGlyphIndex = *(mModel->mVisualModel->mCharactersToGlyph.Begin() + mTextUpdateInfo.mParagraphCharacterIndex); } -void Controller::Impl::ClearFullModelData(OperationsMask operations) +void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) { - if(NO_OPERATION != (GET_LINE_BREAKS & operations)) - { - mModel->mLogicalModel->mLineBreakInfo.Clear(); - mModel->mLogicalModel->mParagraphInfo.Clear(); - } + ControllerImplDataClearer::ClearModelData(*this, startIndex, endIndex, operations); +} - if(NO_OPERATION != (GET_SCRIPTS & operations)) - { - mModel->mLogicalModel->mScriptRuns.Clear(); - } +bool Controller::Impl::UpdateModel(OperationsMask operationsRequired) +{ + return ControllerImplModelUpdater::Update(*this, operationsRequired); +} - if(NO_OPERATION != (VALIDATE_FONTS & operations)) - { - mModel->mLogicalModel->mFontRuns.Clear(); - } +void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle) +{ + SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor); +} - if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count()) +float Controller::Impl::GetDefaultFontLineHeight() +{ + FontId defaultFontId = 0u; + if(nullptr == mFontDefaults) { - if(NO_OPERATION != (BIDI_INFO & operations)) - { - mModel->mLogicalModel->mBidirectionalParagraphInfo.Clear(); - mModel->mLogicalModel->mCharacterDirections.Clear(); - } - - if(NO_OPERATION != (REORDER & operations)) - { - // Free the allocated memory used to store the conversion table in the bidirectional line info run. - for(Vector::Iterator it = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(), - endIt = mModel->mLogicalModel->mBidirectionalLineInfo.End(); - it != endIt; - ++it) - { - BidirectionalLineInfoRun& bidiLineInfo = *it; - - free(bidiLineInfo.visualToLogicalMap); - bidiLineInfo.visualToLogicalMap = NULL; - } - mModel->mLogicalModel->mBidirectionalLineInfo.Clear(); - } + TextAbstraction::FontDescription fontDescription; + defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale); } - - if(NO_OPERATION != (SHAPE_TEXT & operations)) + else { - mModel->mVisualModel->mGlyphs.Clear(); - mModel->mVisualModel->mGlyphsToCharacters.Clear(); - mModel->mVisualModel->mCharactersToGlyph.Clear(); - mModel->mVisualModel->mCharactersPerGlyph.Clear(); - mModel->mVisualModel->mGlyphsPerCharacter.Clear(); - mModel->mVisualModel->mGlyphPositions.Clear(); + defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale); } - if(NO_OPERATION != (LAYOUT & operations)) - { - mModel->mVisualModel->mLines.Clear(); - } + Text::FontMetrics fontMetrics; + mMetrics->GetFontMetrics(defaultFontId, fontMetrics); - if(NO_OPERATION != (COLOR & operations)) - { - mModel->mVisualModel->mColorIndices.Clear(); - mModel->mVisualModel->mBackgroundColorIndices.Clear(); - } + return (fontMetrics.ascender - fontMetrics.descender); } -void Controller::Impl::ClearCharacterModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) +bool Controller::Impl::SetDefaultLineSpacing(float lineSpacing) { - const CharacterIndex endIndexPlusOne = endIndex + 1u; - - if(NO_OPERATION != (GET_LINE_BREAKS & operations)) + if(std::fabs(lineSpacing - mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000) { - // Clear the line break info. - LineBreakInfo* lineBreakInfoBuffer = mModel->mLogicalModel->mLineBreakInfo.Begin(); + mLayoutEngine.SetDefaultLineSpacing(lineSpacing); + mRecalculateNaturalSize = true; - mModel->mLogicalModel->mLineBreakInfo.Erase(lineBreakInfoBuffer + startIndex, - lineBreakInfoBuffer + endIndexPlusOne); - - // Clear the paragraphs. - ClearCharacterRuns(startIndex, - endIndex, - mModel->mLogicalModel->mParagraphInfo); + RelayoutForNewLineSize(); + return true; } + return false; +} - if(NO_OPERATION != (GET_SCRIPTS & operations)) +bool Controller::Impl::SetDefaultLineSize(float lineSize) +{ + if(std::fabs(lineSize - mLayoutEngine.GetDefaultLineSize()) > Math::MACHINE_EPSILON_1000) { - // Clear the scripts. - ClearCharacterRuns(startIndex, - endIndex, - mModel->mLogicalModel->mScriptRuns); - } + mLayoutEngine.SetDefaultLineSize(lineSize); + mRecalculateNaturalSize = true; - if(NO_OPERATION != (VALIDATE_FONTS & operations)) - { - // Clear the fonts. - ClearCharacterRuns(startIndex, - endIndex, - mModel->mLogicalModel->mFontRuns); + RelayoutForNewLineSize(); + return true; } + return false; +} - if(0u != mModel->mLogicalModel->mBidirectionalParagraphInfo.Count()) +string Controller::Impl::GetSelectedText() +{ + string text; + if(EventData::SELECTING == mEventData->mState) { - if(NO_OPERATION != (BIDI_INFO & operations)) - { - // Clear the bidirectional paragraph info. - ClearCharacterRuns(startIndex, - endIndex, - mModel->mLogicalModel->mBidirectionalParagraphInfo); - - // Clear the character's directions. - CharacterDirection* characterDirectionsBuffer = mModel->mLogicalModel->mCharacterDirections.Begin(); - - mModel->mLogicalModel->mCharacterDirections.Erase(characterDirectionsBuffer + startIndex, - characterDirectionsBuffer + endIndexPlusOne); - } - - if(NO_OPERATION != (REORDER & operations)) - { - uint32_t startRemoveIndex = mModel->mLogicalModel->mBidirectionalLineInfo.Count(); - uint32_t endRemoveIndex = startRemoveIndex; - ClearCharacterRuns(startIndex, - endIndex, - mModel->mLogicalModel->mBidirectionalLineInfo, - startRemoveIndex, - endRemoveIndex); - - BidirectionalLineInfoRun* bidirectionalLineInfoBuffer = mModel->mLogicalModel->mBidirectionalLineInfo.Begin(); - - // Free the allocated memory used to store the conversion table in the bidirectional line info run. - for(Vector::Iterator it = bidirectionalLineInfoBuffer + startRemoveIndex, - endIt = bidirectionalLineInfoBuffer + endRemoveIndex; - it != endIt; - ++it) - { - BidirectionalLineInfoRun& bidiLineInfo = *it; - - free(bidiLineInfo.visualToLogicalMap); - bidiLineInfo.visualToLogicalMap = NULL; - } - - mModel->mLogicalModel->mBidirectionalLineInfo.Erase(bidirectionalLineInfoBuffer + startRemoveIndex, - bidirectionalLineInfoBuffer + endRemoveIndex); - } + RetrieveSelection(text, false); } + return text; } -void Controller::Impl::ClearGlyphModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) +string Controller::Impl::CopyText() { - const CharacterIndex endIndexPlusOne = endIndex + 1u; - const Length numberOfCharactersRemoved = endIndexPlusOne - startIndex; - - // Convert the character index to glyph index before deleting the character to glyph and the glyphs per character buffers. - GlyphIndex* charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - Length* glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin(); - - const GlyphIndex endGlyphIndexPlusOne = *(charactersToGlyphBuffer + endIndex) + *(glyphsPerCharacterBuffer + endIndex); - const Length numberOfGlyphsRemoved = endGlyphIndexPlusOne - mTextUpdateInfo.mStartGlyphIndex; - - if(NO_OPERATION != (SHAPE_TEXT & operations)) - { - // Update the character to glyph indices. - for(Vector::Iterator it = charactersToGlyphBuffer + endIndexPlusOne, - endIt = charactersToGlyphBuffer + mModel->mVisualModel->mCharactersToGlyph.Count(); - it != endIt; - ++it) - { - CharacterIndex& index = *it; - index -= numberOfGlyphsRemoved; - } - - // Clear the character to glyph conversion table. - mModel->mVisualModel->mCharactersToGlyph.Erase(charactersToGlyphBuffer + startIndex, - charactersToGlyphBuffer + endIndexPlusOne); - - // Clear the glyphs per character table. - mModel->mVisualModel->mGlyphsPerCharacter.Erase(glyphsPerCharacterBuffer + startIndex, - glyphsPerCharacterBuffer + endIndexPlusOne); + string text; + RetrieveSelection(text, false); + SendSelectionToClipboard(false); // Text not modified - // Clear the glyphs buffer. - GlyphInfo* glyphsBuffer = mModel->mVisualModel->mGlyphs.Begin(); - mModel->mVisualModel->mGlyphs.Erase(glyphsBuffer + mTextUpdateInfo.mStartGlyphIndex, - glyphsBuffer + endGlyphIndexPlusOne); + mEventData->mUpdateCursorPosition = true; - CharacterIndex* glyphsToCharactersBuffer = mModel->mVisualModel->mGlyphsToCharacters.Begin(); - - // Update the glyph to character indices. - for(Vector::Iterator it = glyphsToCharactersBuffer + endGlyphIndexPlusOne, - endIt = glyphsToCharactersBuffer + mModel->mVisualModel->mGlyphsToCharacters.Count(); - it != endIt; - ++it) - { - CharacterIndex& index = *it; - index -= numberOfCharactersRemoved; - } + RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup - // Clear the glyphs to characters buffer. - mModel->mVisualModel->mGlyphsToCharacters.Erase(glyphsToCharactersBuffer + mTextUpdateInfo.mStartGlyphIndex, - glyphsToCharactersBuffer + endGlyphIndexPlusOne); - - // Clear the characters per glyph buffer. - Length* charactersPerGlyphBuffer = mModel->mVisualModel->mCharactersPerGlyph.Begin(); - mModel->mVisualModel->mCharactersPerGlyph.Erase(charactersPerGlyphBuffer + mTextUpdateInfo.mStartGlyphIndex, - charactersPerGlyphBuffer + endGlyphIndexPlusOne); + return text; +} - // Should pass if mGlyphPositions has already been cleared in Controller::Relayouter::Relayout - if(0u != mModel->mVisualModel->mGlyphPositions.Count()) - { - // Clear the positions buffer. - Vector2* positionsBuffer = mModel->mVisualModel->mGlyphPositions.Begin(); - mModel->mVisualModel->mGlyphPositions.Erase(positionsBuffer + mTextUpdateInfo.mStartGlyphIndex, - positionsBuffer + endGlyphIndexPlusOne); - } - } +string Controller::Impl::CutText() +{ + string text; + RetrieveSelection(text, false); - if(NO_OPERATION != (LAYOUT & operations)) + if(!IsEditable()) { - // Clear the lines. - uint32_t startRemoveIndex = mModel->mVisualModel->mLines.Count(); - uint32_t endRemoveIndex = startRemoveIndex; - ClearCharacterRuns(startIndex, - endIndex, - mModel->mVisualModel->mLines, - startRemoveIndex, - endRemoveIndex); - - // Will update the glyph runs. - startRemoveIndex = mModel->mVisualModel->mLines.Count(); - endRemoveIndex = startRemoveIndex; - ClearGlyphRuns(mTextUpdateInfo.mStartGlyphIndex, - endGlyphIndexPlusOne - 1u, - mModel->mVisualModel->mLines, - startRemoveIndex, - endRemoveIndex); - - // Set the line index from where to insert the new laid-out lines. - mTextUpdateInfo.mStartLineIndex = startRemoveIndex; - - LineRun* linesBuffer = mModel->mVisualModel->mLines.Begin(); - mModel->mVisualModel->mLines.Erase(linesBuffer + startRemoveIndex, - linesBuffer + endRemoveIndex); + return EMPTY_STRING; } - if(NO_OPERATION != (COLOR & operations)) - { - if(0u != mModel->mVisualModel->mColorIndices.Count()) - { - ColorIndex* colorIndexBuffer = mModel->mVisualModel->mColorIndices.Begin(); - mModel->mVisualModel->mColorIndices.Erase(colorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex, - colorIndexBuffer + endGlyphIndexPlusOne); - } - - if(0u != mModel->mVisualModel->mBackgroundColorIndices.Count()) - { - ColorIndex* backgroundColorIndexBuffer = mModel->mVisualModel->mBackgroundColorIndices.Begin(); - mModel->mVisualModel->mBackgroundColorIndices.Erase(backgroundColorIndexBuffer + mTextUpdateInfo.mStartGlyphIndex, - backgroundColorIndexBuffer + endGlyphIndexPlusOne); - } - } -} + SendSelectionToClipboard(true); // Synchronous call to modify text + mOperationsPending = ALL_OPERATIONS; -void Controller::Impl::ClearModelData(CharacterIndex startIndex, CharacterIndex endIndex, OperationsMask operations) -{ - if(mTextUpdateInfo.mClearAll || - ((0u == startIndex) && - (mTextUpdateInfo.mPreviousNumberOfCharacters == endIndex + 1u))) + if((0u != mModel->mLogicalModel->mText.Count()) || + !IsPlaceholderAvailable()) { - ClearFullModelData(operations); + QueueModifyEvent(ModifyEvent::TEXT_DELETED); } else { - // Clear the model data related with characters. - ClearCharacterModelData(startIndex, endIndex, operations); - - // Clear the model data related with glyphs. - ClearGlyphModelData(startIndex, endIndex, operations); + PlaceholderHandler::ShowPlaceholderText(*this); } - // The estimated number of lines. Used to avoid reallocations when layouting. - mTextUpdateInfo.mEstimatedNumberOfLines = std::max(mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count()); - - mModel->mVisualModel->ClearCaches(); -} - -bool Controller::Impl::UpdateModel(OperationsMask operationsRequired) -{ - return ControllerImplModelUpdater::Update(*this, operationsRequired); -} + mEventData->mUpdateCursorPosition = true; + mEventData->mScrollAfterDelete = true; -void Controller::Impl::RetrieveDefaultInputStyle(InputStyle& inputStyle) -{ - SetDefaultInputStyle(inputStyle, mFontDefaults, mTextColor); -} + RequestRelayout(); -float Controller::Impl::GetDefaultFontLineHeight() -{ - FontId defaultFontId = 0u; - if(nullptr == mFontDefaults) - { - TextAbstraction::FontDescription fontDescription; - defaultFontId = mFontClient.GetFontId(fontDescription, TextAbstraction::FontClient::DEFAULT_POINT_SIZE * mFontSizeScale); - } - else + if(nullptr != mEditableControlInterface) { - defaultFontId = mFontDefaults->GetFontId(mFontClient, mFontDefaults->mDefaultPointSize * mFontSizeScale); + mEditableControlInterface->TextChanged(true); } - - Text::FontMetrics fontMetrics; - mMetrics->GetFontMetrics(defaultFontId, fontMetrics); - - return (fontMetrics.ascender - fontMetrics.descender); + return text; } void Controller::Impl::SetTextSelectionRange(const uint32_t* pStart, const uint32_t* pEnd) @@ -783,6 +890,26 @@ void Controller::Impl::SetEditable(bool editable) if(mEventData) { mEventData->mEditingEnabled = editable; + + if(mEventData->mDecorator) + { + mEventData->mDecorator->SetEditable(editable); + } + } +} + +void Controller::Impl::UpdateAfterFontChange(const std::string& newDefaultFont) +{ + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::UpdateAfterFontChange\n"); + + if(!mFontDefaults->familyDefined) // If user defined font then should not update when system font changes + { + DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::UpdateAfterFontChange newDefaultFont(%s)\n", newDefaultFont.c_str()); + mFontDefaults->mFontDescription.family = newDefaultFont; + + ClearFontData(); + + RequestRelayout(); } } @@ -948,277 +1075,62 @@ void Controller::Impl::SetPopupButtons() bool isEditable = IsEditable(); TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE; - if(EventData::SELECTING == mEventData->mState) - { - buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY); - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT); - } - - if(!IsClipboardEmpty()) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); - } - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); - } - - if(!mEventData->mAllTextSelected) - { - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL)); - } - } - else if(EventData::EDITING_WITH_POPUP == mEventData->mState) - { - if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText()) - { - buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL); - } - - if(!IsClipboardEmpty()) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); - } - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); - } - } - else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState) - { - if(!IsClipboardEmpty()) - { - if(isEditable) - { - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); - } - buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); - } - } - - mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow); -} - -void Controller::Impl::ChangeState(EventData::State newState) -{ - if(nullptr == mEventData) - { - // Nothing to do if there is no text input. - return; - } - - DALI_LOG_INFO(gLogFilter, Debug::General, "ChangeState state:%d newstate:%d\n", mEventData->mState, newState); - - if(mEventData->mState != newState) - { - mEventData->mPreviousState = mEventData->mState; - mEventData->mState = newState; - - switch(mEventData->mState) - { - case EventData::INACTIVE: - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - mEventData->mDecorator->SetPopupActive(false); - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::INTERRUPTED: - { - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - mEventData->mDecorator->SetPopupActive(false); - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::SELECTING: - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); - } - mEventData->mDecorator->SetHighlightActive(true); - if(mEventData->mGrabHandlePopupEnabled) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive(true); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING: - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - if(mEventData->mCursorBlinkEnabled) - { - mEventData->mDecorator->StartCursorBlink(); - } - // Grab handle is not shown until a tap is received whilst EDITING - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - if(mEventData->mGrabHandlePopupEnabled) - { - mEventData->mDecorator->SetPopupActive(false); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING_WITH_POPUP: - { - DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_POPUP \n", newState); - - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - if(mEventData->mCursorBlinkEnabled) - { - mEventData->mDecorator->StartCursorBlink(); - } - if(mEventData->mSelectionEnabled) - { - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - } - else if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true); - } - if(mEventData->mGrabHandlePopupEnabled) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive(true); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::EDITING_WITH_GRAB_HANDLE: - { - DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_GRAB_HANDLE \n", newState); + if(EventData::SELECTING == mEventData->mState) + { + buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::COPY); + if(isEditable) + { + buttonsToShow = TextSelectionPopup::Buttons(buttonsToShow | TextSelectionPopup::CUT); + } - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - if(mEventData->mCursorBlinkEnabled) - { - mEventData->mDecorator->StartCursorBlink(); - } - // Grab handle is not shown until a tap is received whilst EDITING - if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true); - } - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - if(mEventData->mGrabHandlePopupEnabled) - { - mEventData->mDecorator->SetPopupActive(false); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::SELECTION_HANDLE_PANNING: - { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, true); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, true); - } - mEventData->mDecorator->SetHighlightActive(true); - if(mEventData->mGrabHandlePopupEnabled) - { - mEventData->mDecorator->SetPopupActive(false); - } - mEventData->mDecoratorUpdated = true; - break; - } - case EventData::GRAB_HANDLE_PANNING: + if(!IsClipboardEmpty()) + { + if(isEditable) { - DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GRAB_HANDLE_PANNING \n", newState); - - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - if(mEventData->mCursorBlinkEnabled) - { - mEventData->mDecorator->StartCursorBlink(); - } - if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true); - } - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); - if(mEventData->mGrabHandlePopupEnabled) - { - mEventData->mDecorator->SetPopupActive(false); - } - mEventData->mDecoratorUpdated = true; - break; + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); } - case EventData::EDITING_WITH_PASTE_POPUP: - { - DALI_LOG_INFO(gLogFilter, Debug::Verbose, "EDITING_WITH_PASTE_POPUP \n", newState); - - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_PRIMARY); - if(mEventData->mCursorBlinkEnabled) - { - mEventData->mDecorator->StartCursorBlink(); - } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); + } - if(mEventData->mGrabHandleEnabled) - { - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, true); - } - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(false); + if(!mEventData->mAllTextSelected) + { + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::SELECT_ALL)); + } + } + else if(EventData::EDITING_WITH_POPUP == mEventData->mState) + { + if(mModel->mLogicalModel->mText.Count() && !IsShowingPlaceholderText()) + { + buttonsToShow = TextSelectionPopup::Buttons(TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL); + } - if(mEventData->mGrabHandlePopupEnabled) - { - SetPopupButtons(); - mEventData->mDecorator->SetPopupActive(true); - } - mEventData->mDecoratorUpdated = true; - break; + if(!IsClipboardEmpty()) + { + if(isEditable) + { + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); } - case EventData::TEXT_PANNING: + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); + } + } + else if(EventData::EDITING_WITH_PASTE_POPUP == mEventData->mState) + { + if(!IsClipboardEmpty()) + { + if(isEditable) { - mEventData->mDecorator->SetActiveCursor(ACTIVE_CURSOR_NONE); - mEventData->mDecorator->StopCursorBlink(); - mEventData->mDecorator->SetHandleActive(GRAB_HANDLE, false); - if(mEventData->mDecorator->IsHandleActive(LEFT_SELECTION_HANDLE) || - mEventData->mDecorator->IsHandleActive(RIGHT_SELECTION_HANDLE)) - { - mEventData->mDecorator->SetHandleActive(LEFT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHandleActive(RIGHT_SELECTION_HANDLE, false); - mEventData->mDecorator->SetHighlightActive(true); - } - - if(mEventData->mGrabHandlePopupEnabled) - { - mEventData->mDecorator->SetPopupActive(false); - } - - mEventData->mDecoratorUpdated = true; - break; + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::PASTE)); } + buttonsToShow = TextSelectionPopup::Buttons((buttonsToShow | TextSelectionPopup::CLIPBOARD)); } } + + mEventData->mDecorator->SetEnabledPopupButtons(buttonsToShow); +} + +void Controller::Impl::ChangeState(EventData::State newState) +{ + ChangeTextControllerState(*this, newState); } void Controller::Impl::GetCursorPosition(CharacterIndex logical, @@ -1550,186 +1462,138 @@ void Controller::Impl::RequestRelayout() } } -Actor Controller::Impl::CreateBackgroundActor() +void Controller::Impl::RelayoutForNewLineSize() { - // NOTE: Currently we only support background color for left-to-right text. + // relayout all characters + mTextUpdateInfo.mCharacterIndex = 0; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + mOperationsPending = static_cast(mOperationsPending | LAYOUT); - Actor actor; - - Length numberOfGlyphs = mView.GetNumberOfGlyphs(); - if(numberOfGlyphs > 0u) + //remove selection + if(mEventData && mEventData->mState == EventData::SELECTING) { - Vector glyphs; - glyphs.Resize(numberOfGlyphs); - - Vector positions; - positions.Resize(numberOfGlyphs); - - // Get the line where the glyphs are laid-out. - const LineRun* lineRun = mModel->mVisualModel->mLines.Begin(); - float alignmentOffset = lineRun->alignmentOffset; - numberOfGlyphs = mView.GetGlyphs(glyphs.Begin(), - positions.Begin(), - alignmentOffset, - 0u, - numberOfGlyphs); - - glyphs.Resize(numberOfGlyphs); - positions.Resize(numberOfGlyphs); + ChangeState(EventData::EDITING); + } - const GlyphInfo* const glyphsBuffer = glyphs.Begin(); - const Vector2* const positionsBuffer = positions.Begin(); + RequestRelayout(); +} - BackgroundMesh mesh; - mesh.mVertices.Reserve(4u * glyphs.Size()); - mesh.mIndices.Reserve(6u * glyphs.Size()); +bool Controller::Impl::IsInputStyleChangedSignalsQueueEmpty() +{ + return (NULL == mEventData) || (0u == mEventData->mInputStyleChangedQueue.Count()); +} - const Vector2 textSize = mView.GetLayoutSize(); +void Controller::Impl::ProcessInputStyleChangedSignals() +{ + if(mEventData) + { + if(mEditableControlInterface) + { + // Emit the input style changed signal for each mask + std::for_each(mEventData->mInputStyleChangedQueue.begin(), + mEventData->mInputStyleChangedQueue.end(), + [&](const auto mask) { mEditableControlInterface->InputStyleChanged(mask); } ); + } - const float offsetX = alignmentOffset + textSize.width * 0.5f; - const float offsetY = textSize.height * 0.5f; + mEventData->mInputStyleChangedQueue.Clear(); + } +} - const Vector4* const backgroundColorsBuffer = mView.GetBackgroundColors(); - const ColorIndex* const backgroundColorIndicesBuffer = mView.GetBackgroundColorIndices(); - const Vector4& defaultBackgroundColor = mModel->mVisualModel->IsBackgroundEnabled() ? mModel->mVisualModel->GetBackgroundColor() : Color::TRANSPARENT; +void Controller::Impl::ScrollBy(Vector2 scroll) +{ + if(mEventData && (fabs(scroll.x) > Math::MACHINE_EPSILON_0 || fabs(scroll.y) > Math::MACHINE_EPSILON_0)) + { + const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize(); + const Vector2 currentScroll = mModel->mScrollPosition; - Vector4 quad; - uint32_t numberOfQuads = 0u; - Length yLineOffset = 0; - Length prevLineIndex = 0; - LineIndex lineIndex; - Length numberOfLines; + scroll.x = -scroll.x; + scroll.y = -scroll.y; - for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i) + if(fabs(scroll.x) > Math::MACHINE_EPSILON_0) { - const GlyphInfo& glyph = *(glyphsBuffer + i); - - // Get the background color of the character. - // The color index zero is reserved for the default background color (i.e. Color::TRANSPARENT) - const bool isMarkupBackground = mView.IsMarkupBackgroundColorSet(); - const ColorIndex backgroundColorIndex = isMarkupBackground ? *(backgroundColorIndicesBuffer + i) : 0u; - const bool isDefaultBackgroundColor = (0u == backgroundColorIndex); - const Vector4& backgroundColor = isDefaultBackgroundColor ? defaultBackgroundColor : *(backgroundColorsBuffer + backgroundColorIndex - 1u); - - mModel->mVisualModel->GetNumberOfLines(i, 1, lineIndex, numberOfLines); - Length lineHeight = lineRun[lineIndex].ascender + -(lineRun[lineIndex].descender) + lineRun[lineIndex].lineSpacing; + mModel->mScrollPosition.x += scroll.x; + ClampHorizontalScroll(layoutSize); + } - if(lineIndex != prevLineIndex) - { - yLineOffset += lineHeight; - } + if(fabs(scroll.y) > Math::MACHINE_EPSILON_0) + { + mModel->mScrollPosition.y += scroll.y; + ClampVerticalScroll(layoutSize); + } - // Only create quads for glyphs with a background color - if(backgroundColor != Color::TRANSPARENT) - { - const Vector2 position = *(positionsBuffer + i); + if(mModel->mScrollPosition != currentScroll) + { + mEventData->mDecorator->UpdatePositions(mModel->mScrollPosition - currentScroll); + RequestRelayout(); + } + } +} - if(i == 0u && glyphSize == 1u) // Only one glyph in the whole text - { - quad.x = position.x; - quad.y = yLineOffset; - quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width); - quad.w = lineHeight; - } - else if((lineIndex != prevLineIndex) || (i == 0u)) // The first glyph in the line - { - quad.x = position.x; - quad.y = yLineOffset; - quad.z = quad.x - glyph.xBearing + glyph.advance; - quad.w = quad.y + lineHeight; - } - else if(i == glyphSize - 1u) // The last glyph in the whole text - { - quad.x = position.x - glyph.xBearing; - quad.y = yLineOffset; - quad.z = quad.x + std::max(glyph.advance, glyph.xBearing + glyph.width); - quad.w = quad.y + lineHeight; - } - else // The glyph in the middle of the text - { - quad.x = position.x - glyph.xBearing; - quad.y = yLineOffset; - quad.z = quad.x + glyph.advance; - quad.w = quad.y + lineHeight; - } +float Controller::Impl::GetHorizontalScrollPosition() +{ + // Scroll values are negative internally so we convert them to positive numbers + return mEventData ? -mModel->mScrollPosition.x : 0.0f; +} - BackgroundVertex vertex; - - // Top left - vertex.mPosition.x = quad.x - offsetX; - vertex.mPosition.y = quad.y - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack(vertex); - - // Top right - vertex.mPosition.x = quad.z - offsetX; - vertex.mPosition.y = quad.y - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack(vertex); - - // Bottom left - vertex.mPosition.x = quad.x - offsetX; - vertex.mPosition.y = quad.w - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack(vertex); - - // Bottom right - vertex.mPosition.x = quad.z - offsetX; - vertex.mPosition.y = quad.w - offsetY; - vertex.mColor = backgroundColor; - mesh.mVertices.PushBack(vertex); - - // Six indices in counter clockwise winding - mesh.mIndices.PushBack(1u + 4 * numberOfQuads); - mesh.mIndices.PushBack(0u + 4 * numberOfQuads); - mesh.mIndices.PushBack(2u + 4 * numberOfQuads); - mesh.mIndices.PushBack(2u + 4 * numberOfQuads); - mesh.mIndices.PushBack(3u + 4 * numberOfQuads); - mesh.mIndices.PushBack(1u + 4 * numberOfQuads); - - numberOfQuads++; - } +float Controller::Impl::GetVerticalScrollPosition() +{ + // Scroll values are negative internally so we convert them to positive numbers + return mEventData ? -mModel->mScrollPosition.y : 0.0f; +} - if(lineIndex != prevLineIndex) - { - prevLineIndex = lineIndex; - } - } +Vector3 Controller::Impl::GetAnchorPosition(Anchor anchor) const +{ + //TODO + return Vector3(10.f, 10.f, 10.f); +} - // Only create the background actor if there are glyphs with background color - if(mesh.mVertices.Count() > 0u) - { - Property::Map quadVertexFormat; - quadVertexFormat["aPosition"] = Property::VECTOR2; - quadVertexFormat["aColor"] = Property::VECTOR4; +Vector2 Controller::Impl::GetAnchorSize(Anchor anchor) const +{ + //TODO + return Vector2(10.f, 10.f); +} - VertexBuffer quadVertices = VertexBuffer::New(quadVertexFormat); - quadVertices.SetData(&mesh.mVertices[0], mesh.mVertices.Size()); +Toolkit::TextAnchor Controller::Impl::CreateAnchorActor(Anchor anchor) +{ + auto actor = Toolkit::TextAnchor::New(); + actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + const Vector3 anchorPosition = GetAnchorPosition(anchor); + actor.SetProperty(Actor::Property::POSITION, anchorPosition); + const Vector2 anchorSize = GetAnchorSize(anchor); + actor.SetProperty(Actor::Property::SIZE, anchorSize); + std::string anchorText(mModel->mLogicalModel->mText.Begin() + anchor.startIndex, mModel->mLogicalModel->mText.Begin() + anchor.endIndex); + actor.SetProperty(Actor::Property::NAME, anchorText); + actor.SetProperty(Toolkit::TextAnchor::Property::URI, std::string(anchor.href)); + actor.SetProperty(Toolkit::TextAnchor::Property::START_CHARACTER_INDEX, static_cast(anchor.startIndex)); + actor.SetProperty(Toolkit::TextAnchor::Property::END_CHARACTER_INDEX, static_cast(anchor.endIndex)); + return actor; +} - Geometry quadGeometry = Geometry::New(); - quadGeometry.AddVertexBuffer(quadVertices); - quadGeometry.SetIndexBuffer(&mesh.mIndices[0], mesh.mIndices.Size()); +void Controller::Impl::GetAnchorActors(std::vector& anchorActors) +{ + /* TODO: Now actors are created/destroyed in every "RenderText" function call. Even when we add just 1 character, + we need to create and destroy potentially many actors. Some optimization can be considered here. + Maybe a "dirty" flag in mLogicalModel? */ + anchorActors.clear(); + for(auto& anchor : mModel->mLogicalModel->mAnchors) + { + auto actor = CreateAnchorActor(anchor); + anchorActors.push_back(actor); + } +} - if(!mShaderBackground) - { - mShaderBackground = Shader::New(SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_VERT, SHADER_TEXT_CONTROLLER_BACKGROUND_SHADER_FRAG); - } +int32_t Controller::Impl::GetAnchorIndex(size_t characterOffset) const +{ + Vector::Iterator it = mModel->mLogicalModel->mAnchors.Begin(); - Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, mShaderBackground); - renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON); - renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT); - - actor = Actor::New(); - actor.SetProperty(Dali::Actor::Property::NAME, "TextBackgroundColorActor"); - actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); - actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); - actor.SetProperty(Actor::Property::SIZE, textSize); - actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR); - actor.AddRenderer(renderer); - } + while(it != mModel->mLogicalModel->mAnchors.End() && (it->startIndex > characterOffset || it->endIndex <= characterOffset)) + { + it++; } - return actor; + return it == mModel->mLogicalModel->mAnchors.End() ? -1 : it - mModel->mLogicalModel->mAnchors.Begin(); } void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns) @@ -1758,4 +1622,197 @@ void Controller::Impl::CopyUnderlinedFromLogicalToVisualModels(bool shouldClearP } } +void Controller::Impl::SetAutoScrollEnabled(bool enable) +{ + if(mLayoutEngine.GetLayout() == Layout::Engine::SINGLE_LINE_BOX) + { + mOperationsPending = static_cast(mOperationsPending | + LAYOUT | + ALIGN | + UPDATE_LAYOUT_SIZE | + REORDER); + + if(enable) + { + DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled for SINGLE_LINE_BOX\n"); + mOperationsPending = static_cast(mOperationsPending | UPDATE_DIRECTION); + } + else + { + DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::SetAutoScrollEnabled Disabling autoscroll\n"); + } + + mIsAutoScrollEnabled = enable; + RequestRelayout(); + } + else + { + DALI_LOG_WARNING("Attempted AutoScrolling on a non SINGLE_LINE_BOX, request ignored\n"); + mIsAutoScrollEnabled = false; + } +} + +void Controller::Impl::SetEnableCursorBlink(bool enable) +{ + DALI_ASSERT_DEBUG(NULL != mEventData && "TextInput disabled"); + + if(mEventData) + { + mEventData->mCursorBlinkEnabled = enable; + + if(!enable && mEventData->mDecorator) + { + mEventData->mDecorator->StopCursorBlink(); + } + } +} + +void Controller::Impl::SetMultiLineEnabled(bool enable) +{ + const Layout::Engine::Type layout = enable ? Layout::Engine::MULTI_LINE_BOX : Layout::Engine::SINGLE_LINE_BOX; + + if(layout != mLayoutEngine.GetLayout()) + { + // Set the layout type. + mLayoutEngine.SetLayout(layout); + + // Set the flags to redo the layout operations + const OperationsMask layoutOperations = static_cast(LAYOUT | + UPDATE_LAYOUT_SIZE | + ALIGN | + REORDER); + + mTextUpdateInfo.mFullRelayoutNeeded = true; + mOperationsPending = static_cast(mOperationsPending | layoutOperations); + + // Need to recalculate natural size + mRecalculateNaturalSize = true; + + RequestRelayout(); + } +} + +void Controller::Impl::SetHorizontalAlignment(Text::HorizontalAlignment::Type alignment) +{ + if(alignment != mModel->mHorizontalAlignment) + { + // Set the alignment. + mModel->mHorizontalAlignment = alignment; + + // Set the flag to redo the alignment operation. + mOperationsPending = static_cast(mOperationsPending | ALIGN); + + if(mEventData) + { + mEventData->mUpdateAlignment = true; + + // Update the cursor if it's in editing mode + if(EventData::IsEditingState(mEventData->mState)) + { + ChangeState(EventData::EDITING); + mEventData->mUpdateCursorPosition = true; + } + } + + RequestRelayout(); + } +} + +void Controller::Impl::SetVerticalAlignment(VerticalAlignment::Type alignment) +{ + if(alignment != mModel->mVerticalAlignment) + { + // Set the alignment. + mModel->mVerticalAlignment = alignment; + mOperationsPending = static_cast(mOperationsPending | ALIGN); + RequestRelayout(); + } +} + +void Controller::Impl::SetLineWrapMode(Text::LineWrap::Mode lineWrapMode) +{ + if(lineWrapMode != mModel->mLineWrapMode) + { + // Update Text layout for applying wrap mode + mOperationsPending = static_cast(mOperationsPending | + ALIGN | + LAYOUT | + UPDATE_LAYOUT_SIZE | + REORDER); + + if((mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) || + (mModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED) || (lineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED)) // hyphen is treated as line break + { + mOperationsPending = static_cast(mOperationsPending | GET_LINE_BREAKS); + } + + // Set the text wrap mode. + mModel->mLineWrapMode = lineWrapMode; + + mTextUpdateInfo.mCharacterIndex = 0u; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + + // Request relayout + RequestRelayout(); + } +} + +void Controller::Impl::SetDefaultColor(const Vector4& color) +{ + mTextColor = color; + + if(!IsShowingPlaceholderText()) + { + mModel->mVisualModel->SetTextColor(color); + mModel->mLogicalModel->mColorRuns.Clear(); + mOperationsPending = static_cast(mOperationsPending | COLOR); + RequestRelayout(); + } +} + +void Controller::Impl::ClearFontData() +{ + if(mFontDefaults) + { + mFontDefaults->mFontId = 0u; // Remove old font ID + } + + // Set flags to update the model. + mTextUpdateInfo.mCharacterIndex = 0u; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mModel->mLogicalModel->mText.Count(); + + mTextUpdateInfo.mClearAll = true; + mTextUpdateInfo.mFullRelayoutNeeded = true; + mRecalculateNaturalSize = true; + + mOperationsPending = static_cast(mOperationsPending | + VALIDATE_FONTS | + SHAPE_TEXT | + BIDI_INFO | + GET_GLYPH_METRICS | + LAYOUT | + UPDATE_LAYOUT_SIZE | + REORDER | + ALIGN); +} + +void Controller::Impl::ClearStyleData() +{ + mModel->mLogicalModel->mColorRuns.Clear(); + mModel->mLogicalModel->ClearFontDescriptionRuns(); +} + + +void Controller::Impl::ResetScrollPosition() +{ + if(mEventData) + { + // Reset the scroll position. + mModel->mScrollPosition = Vector2::ZERO; + mEventData->mScrollAfterUpdatePosition = true; + } +} + } // namespace Dali::Toolkit::Text