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=fa48e52189e8af5a7e76a5ea2e1830437dd0dd0e;hp=da7d92943081f54548cc1ab935672d82990d64d1;hb=41d36118a13932bcf1db39b780ac437fcda8aa08;hpb=3705a737006a61c9796ee5f237c88d623fc65cbc diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp old mode 100644 new mode 100755 index da7d929..fa48e52 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -20,10 +20,12 @@ // EXTERNAL INCLUDES #include +#include #include #include // INTERNAL INCLUDES +#include #include #include #include @@ -34,6 +36,8 @@ #include #include +using namespace Dali; + namespace { @@ -55,7 +59,44 @@ struct SelectionBoxInfo const float MAX_FLOAT = std::numeric_limits::max(); const float MIN_FLOAT = std::numeric_limits::min(); const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction -const uint32_t STAR = 0x2A; + +#define MAKE_SHADER(A)#A + +const char* VERTEX_SHADER_BACKGROUND = MAKE_SHADER( +attribute mediump vec2 aPosition; +attribute mediump vec4 aColor; +varying mediump vec4 vColor; +uniform highp mat4 uMvpMatrix; + +void main() +{ + mediump vec4 position = vec4( aPosition, 0.0, 1.0 ); + gl_Position = uMvpMatrix * position; + vColor = aColor; +} +); + +const char* FRAGMENT_SHADER_BACKGROUND = MAKE_SHADER( +varying mediump vec4 vColor; +uniform lowp vec4 uColor; + +void main() +{ + gl_FragColor = vColor * uColor; +} +); + +struct BackgroundVertex +{ + Vector2 mPosition; ///< Vertex posiiton + Vector4 mColor; ///< Vertex color +}; + +struct BackgroundMesh +{ + Vector< BackgroundVertex > mVertices; ///< container of vertices + Vector< unsigned short > mIndices; ///< container of indices +}; } // namespace @@ -68,12 +109,13 @@ namespace Toolkit namespace Text { -EventData::EventData( DecoratorPtr decorator ) +EventData::EventData( DecoratorPtr decorator, InputMethodContext& inputMethodContext ) : mDecorator( decorator ), - mImfManager(), + mInputMethodContext( inputMethodContext ), + mPlaceholderFont( NULL ), mPlaceholderTextActive(), mPlaceholderTextInactive(), - mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), + mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), // This color has been published in the Public API (placeholder-properties.h). mEventQueue(), mInputStyleChangedQueue(), mPreviousState( INACTIVE ), @@ -84,6 +126,8 @@ EventData::EventData( DecoratorPtr decorator ) mPreEditStartPosition( 0u ), mPreEditLength( 0u ), mCursorHookPositionX( 0.f ), + mDoubleTapAction( Controller::NoTextTap::NO_ACTION ), + mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ), mIsShowingPlaceholderText( false ), mPreEditFlag( false ), mDecoratorUpdated( false ), @@ -97,14 +141,20 @@ EventData::EventData( DecoratorPtr decorator ) mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), mIsLeftHandleSelected( false ), + mIsRightHandleSelected( false ), mUpdateHighlightBox( false ), mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ), mAllTextSelected( false ), mUpdateInputStyle( false ), - mPasswordInput( false ) + mPasswordInput( false ), + mCheckScrollAmount( false ), + mIsPlaceholderPixelSize( false ), + mIsPlaceholderElideEnabled( false ), + mPlaceholderEllipsisFlag( false ), + mShiftSelectionFlag( true ), + mUpdateAlignment( false ) { - mImfManager = ImfManager::Get(); } EventData::~EventData() @@ -172,7 +222,7 @@ bool Controller::Impl::ProcessInputEvents() if( mEventData->mUpdateCursorPosition || mEventData->mUpdateHighlightBox ) { - NotifyImfManager(); + NotifyInputMethodContext(); } // The cursor must also be repositioned after inserts into the model @@ -227,10 +277,25 @@ bool Controller::Impl::ProcessInputEvents() if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) ) { - CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected ) + { + CursorInfo& infoLeft = leftHandleInfo; - const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); - ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight ); + + CursorInfo& infoRight = rightHandleInfo; + + const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight ); + } + else + { + CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + + const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); + ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + } } } @@ -261,6 +326,8 @@ bool Controller::Impl::ProcessInputEvents() mEventData->mUpdateLeftSelectionPosition = false; mEventData->mUpdateRightSelectionPosition = false; mEventData->mUpdateHighlightBox = false; + mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = false; } mEventData->mScrollAfterUpdatePosition = false; @@ -304,9 +371,9 @@ bool Controller::Impl::ProcessInputEvents() return decoratorUpdated; } -void Controller::Impl::NotifyImfManager() +void Controller::Impl::NotifyInputMethodContext() { - if( mEventData && mEventData->mImfManager ) + if( mEventData && mEventData->mInputMethodContext ) { CharacterIndex cursorPosition = GetLogicalCursorPosition(); @@ -322,17 +389,17 @@ void Controller::Impl::NotifyImfManager() cursorPosition -= numberOfWhiteSpaces; } - mEventData->mImfManager.SetCursorPosition( cursorPosition ); - mEventData->mImfManager.NotifyCursorPosition(); + mEventData->mInputMethodContext.SetCursorPosition( cursorPosition ); + mEventData->mInputMethodContext.NotifyCursorPosition(); } } -void Controller::Impl::NotifyImfMultiLineStatus() +void Controller::Impl::NotifyInputMethodContextMultiLineStatus() { - if ( mEventData ) + if ( mEventData && mEventData->mInputMethodContext ) { Text::Layout::Engine::Type layout = mLayoutEngine.GetLayout(); - mEventData->mImfManager.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX ); + mEventData->mInputMethodContext.NotifyTextInputMultiLine( layout == Text::Layout::Engine::MULTI_LINE_BOX ); } } @@ -480,11 +547,6 @@ void Controller::Impl::ClearFullModelData( OperationsMask operations ) mModel->mLogicalModel->mParagraphInfo.Clear(); } - if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) - { - mModel->mLogicalModel->mLineBreakInfo.Clear(); - } - if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) { mModel->mLogicalModel->mScriptRuns.Clear(); @@ -538,6 +600,7 @@ void Controller::Impl::ClearFullModelData( OperationsMask operations ) if( NO_OPERATION != ( COLOR & operations ) ) { mModel->mVisualModel->mColorIndices.Clear(); + mModel->mVisualModel->mBackgroundColorIndices.Clear(); } } @@ -559,15 +622,6 @@ void Controller::Impl::ClearCharacterModelData( CharacterIndex startIndex, Chara mModel->mLogicalModel->mParagraphInfo ); } - if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) - { - // Clear the word break info. - WordBreakInfo* wordBreakInfoBuffer = mModel->mLogicalModel->mWordBreakInfo.Begin(); - - mModel->mLogicalModel->mWordBreakInfo.Erase( wordBreakInfoBuffer + startIndex, - wordBreakInfoBuffer + endIndexPlusOne ); - } - if( NO_OPERATION != ( GET_SCRIPTS & operations ) ) { // Clear the scripts. @@ -730,6 +784,13 @@ void Controller::Impl::ClearGlyphModelData( CharacterIndex startIndex, Character 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 ); + } } } @@ -769,24 +830,16 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) return false; } - Vector utf32CharactersStar; - const Length characterCount = mModel->mLogicalModel->mText.Count(); - const bool isPasswordInput = ( mEventData != NULL && mEventData->mPasswordInput && - !mEventData->mIsShowingPlaceholderText && characterCount > 0 ); - - if (isPasswordInput) + Vector& srcCharacters = mModel->mLogicalModel->mText; + Vector displayCharacters; + bool useHiddenText = false; + if ( mHiddenInput && mEventData != NULL && !mEventData->mIsShowingPlaceholderText) { - utf32CharactersStar.Resize( characterCount ); - - uint32_t* begin = utf32CharactersStar.Begin(); - uint32_t* end = begin + characterCount; - while ( begin < end ) - { - *begin++ = STAR; - } + mHiddenInput->Substitute( srcCharacters,displayCharacters ); + useHiddenText = true; } - Vector& utf32Characters = isPasswordInput ? utf32CharactersStar : mModel->mLogicalModel->mText; + Vector& utf32Characters = useHiddenText ? displayCharacters : srcCharacters; const Length numberOfCharacters = utf32Characters.Count(); // Index to the first character of the first paragraph to be updated. @@ -795,6 +848,36 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) Length paragraphCharacters = 0u; CalculateTextUpdateIndices( paragraphCharacters ); + + // Check whether the indices for updating the text is valid + if ( numberOfCharacters > 0u && + ( mTextUpdateInfo.mParagraphCharacterIndex > numberOfCharacters || + mTextUpdateInfo.mRequestedNumberOfCharacters > numberOfCharacters ) ) + { + std::string currentText; + Utf32ToUtf8( mModel->mLogicalModel->mText.Begin(), numberOfCharacters, currentText ); + + DALI_LOG_ERROR( "Controller::Impl::UpdateModel: mTextUpdateInfo has invalid indices\n" ); + DALI_LOG_ERROR( "Number of characters: %d, current text is: %s\n", numberOfCharacters, currentText.c_str() ); + + // Dump mTextUpdateInfo + DALI_LOG_ERROR( "Dump mTextUpdateInfo:\n" ); + DALI_LOG_ERROR( " mTextUpdateInfo.mCharacterIndex = %u\n", mTextUpdateInfo.mCharacterIndex ); + DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToRemove = %u\n", mTextUpdateInfo.mNumberOfCharactersToRemove ); + DALI_LOG_ERROR( " mTextUpdateInfo.mNumberOfCharactersToAdd = %u\n", mTextUpdateInfo.mNumberOfCharactersToAdd ); + DALI_LOG_ERROR( " mTextUpdateInfo.mPreviousNumberOfCharacters = %u\n", mTextUpdateInfo.mPreviousNumberOfCharacters ); + DALI_LOG_ERROR( " mTextUpdateInfo.mParagraphCharacterIndex = %u\n", mTextUpdateInfo.mParagraphCharacterIndex ); + DALI_LOG_ERROR( " mTextUpdateInfo.mRequestedNumberOfCharacters = %u\n", mTextUpdateInfo.mRequestedNumberOfCharacters ); + DALI_LOG_ERROR( " mTextUpdateInfo.mStartGlyphIndex = %u\n", mTextUpdateInfo.mStartGlyphIndex ); + DALI_LOG_ERROR( " mTextUpdateInfo.mStartLineIndex = %u\n", mTextUpdateInfo.mStartLineIndex ); + DALI_LOG_ERROR( " mTextUpdateInfo.mEstimatedNumberOfLines = %u\n", mTextUpdateInfo.mEstimatedNumberOfLines ); + DALI_LOG_ERROR( " mTextUpdateInfo.mClearAll = %d\n", mTextUpdateInfo.mClearAll ); + DALI_LOG_ERROR( " mTextUpdateInfo.mFullRelayoutNeeded = %d\n", mTextUpdateInfo.mFullRelayoutNeeded ); + DALI_LOG_ERROR( " mTextUpdateInfo.mIsLastCharacterNewParagraph = %d\n", mTextUpdateInfo.mIsLastCharacterNewParagraph ); + + return false; + } + startIndex = mTextUpdateInfo.mParagraphCharacterIndex; if( mTextUpdateInfo.mClearAll || @@ -830,19 +913,6 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) updated = true; } - Vector& wordBreakInfo = mModel->mLogicalModel->mWordBreakInfo; - if( NO_OPERATION != ( GET_WORD_BREAKS & operations ) ) - { - // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines). - wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK ); - - SetWordBreakInfo( utf32Characters, - startIndex, - requestedNumberOfCharacters, - wordBreakInfo ); - updated = true; - } - const bool getScripts = NO_OPERATION != ( GET_SCRIPTS & operations ); const bool validateFonts = NO_OPERATION != ( VALIDATE_FONTS & operations ); @@ -872,10 +942,29 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) // Get the default font's description. TextAbstraction::FontDescription defaultFontDescription; TextAbstraction::PointSize26Dot6 defaultPointSize = TextAbstraction::FontClient::DEFAULT_POINT_SIZE; - if( NULL != mFontDefaults ) + + if( IsShowingPlaceholderText() && mEventData && ( NULL != mEventData->mPlaceholderFont ) ) { + // If the placeholder font is set specifically, only placeholder font is changed. + defaultFontDescription = mEventData->mPlaceholderFont->mFontDescription; + if( mEventData->mPlaceholderFont->sizeDefined ) + { + defaultPointSize = mEventData->mPlaceholderFont->mDefaultPointSize * 64u; + } + } + else if( NULL != mFontDefaults ) + { + // Set the normal font and the placeholder font. defaultFontDescription = mFontDefaults->mFontDescription; - defaultPointSize = mFontDefaults->mDefaultPointSize * 64u; + + if( mTextFitEnabled ) + { + defaultPointSize = mFontDefaults->mFitPointSize * 64u; + } + else + { + defaultPointSize = mFontDefaults->mDefaultPointSize * 64u; + } } // Validates the fonts. If there is a character with no assigned font it sets a default one. @@ -906,7 +995,9 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) lineBreakInfo, startIndex, requestedNumberOfCharacters, - bidirectionalInfo ); + bidirectionalInfo, + mModel->mMatchSystemLanguageDirection, + mLayoutDirection ); if( 0u != bidirectionalInfo.Count() ) { @@ -985,6 +1076,41 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) updated = true; } + if( ( NULL != mEventData ) && + mEventData->mPreEditFlag && + ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) ) + { + Dali::InputMethodContext::PreeditStyle type = mEventData->mInputMethodContext.GetPreeditStyle(); + + switch( type ) + { + case Dali::InputMethodContext::PreeditStyle::UNDERLINE: + { + // Add the underline for the pre-edit text. + const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); + const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin(); + + const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition ); + const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u ); + const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter ); + const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u ); + + GlyphRun underlineRun; + underlineRun.glyphIndex = glyphStart; + underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart; + + mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); + break; + } + // TODO : At this moment, other styles for preedit are not implemented yet. + case Dali::InputMethodContext::PreeditStyle::REVERSE: + case Dali::InputMethodContext::PreeditStyle::HIGHLIGHT: + case Dali::InputMethodContext::PreeditStyle::NONE: + default: + break; + } + } + if( NO_OPERATION != ( COLOR & operations ) ) { // Set the color runs in glyphs. @@ -997,29 +1123,19 @@ bool Controller::Impl::UpdateModel( OperationsMask operationsRequired ) mModel->mVisualModel->mColors, mModel->mVisualModel->mColorIndices ); + // Set the background color runs in glyphs. + SetColorSegmentationInfo( mModel->mLogicalModel->mBackgroundColorRuns, + mModel->mVisualModel->mCharactersToGlyph, + mModel->mVisualModel->mGlyphsPerCharacter, + startIndex, + mTextUpdateInfo.mStartGlyphIndex, + requestedNumberOfCharacters, + mModel->mVisualModel->mBackgroundColors, + mModel->mVisualModel->mBackgroundColorIndices ); + updated = true; } - if( ( NULL != mEventData ) && - mEventData->mPreEditFlag && - ( 0u != mModel->mVisualModel->mCharactersToGlyph.Count() ) ) - { - // Add the underline for the pre-edit text. - const GlyphIndex* const charactersToGlyphBuffer = mModel->mVisualModel->mCharactersToGlyph.Begin(); - const Length* const glyphsPerCharacterBuffer = mModel->mVisualModel->mGlyphsPerCharacter.Begin(); - - const GlyphIndex glyphStart = *( charactersToGlyphBuffer + mEventData->mPreEditStartPosition ); - const CharacterIndex lastPreEditCharacter = mEventData->mPreEditStartPosition + ( ( mEventData->mPreEditLength > 0u ) ? mEventData->mPreEditLength - 1u : 0u ); - const Length numberOfGlyphsLastCharacter = *( glyphsPerCharacterBuffer + lastPreEditCharacter ); - const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + lastPreEditCharacter ) + ( numberOfGlyphsLastCharacter > 1u ? numberOfGlyphsLastCharacter - 1u : 0u ); - - GlyphRun underlineRun; - underlineRun.glyphIndex = glyphStart; - underlineRun.numberOfGlyphs = 1u + glyphEnd - glyphStart; - - // TODO: At the moment the underline runs are only for pre-edit. - mModel->mVisualModel->mUnderlineRuns.PushBack( underlineRun ); - } // The estimated number of lines. Used to avoid reallocations when layouting. mTextUpdateInfo.mEstimatedNumberOfLines = std::max( mModel->mVisualModel->mLines.Count(), mModel->mLogicalModel->mParagraphInfo.Count() ); @@ -1118,30 +1234,49 @@ float Controller::Impl::GetDefaultFontLineHeight() void Controller::Impl::OnCursorKeyEvent( const Event& event ) { - if( NULL == mEventData ) + if( NULL == mEventData || !IsShowingRealText() ) { // Nothing to do if there is no text input. return; } int keyCode = event.p1.mInt; + bool isShiftModifier = event.p2.mBool; + + CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition; if( Dali::DALI_KEY_CURSOR_LEFT == keyCode ) { if( mEventData->mPrimaryCursorPosition > 0u ) { - mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u ); + if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() ) + { + mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition); + } + else + { + mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u ); + } } } else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) { if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition ) { - mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition ); + if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() ) + { + mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition); + } + else + { + mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition ); + } } } - else if( Dali::DALI_KEY_CURSOR_UP == keyCode ) + else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier ) { + // Ignore Shift-Up for text selection for now. + // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1165,14 +1300,19 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); } - else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) + else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier ) { + // Ignore Shift-Down for text selection for now. + // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1197,15 +1337,75 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); + } + } + + if ( !isShiftModifier && mEventData->mState != EventData::SELECTING ) + { + // Update selection position after moving the cursor + mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; + } + + if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag ) + { + // Handle text selection + bool selecting = false; + + if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) + { + // Shift-Left/Right to select the text + int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition; + if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary + { + mEventData->mRightSelectionPosition += cursorPositionDelta; + } + selecting = true; + } + else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) + { + // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text + selecting = true; + } + + if ( selecting ) + { + // Notify the cursor position to the InputMethodContext. + if( mEventData->mInputMethodContext ) + { + mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition ); + mEventData->mInputMethodContext.NotifyCursorPosition(); + } + + ChangeState( EventData::SELECTING ); + + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mUpdateHighlightBox = true; + + // Hide the text selection popup if select the text using keyboard instead of moving grab handles + if( mEventData->mGrabHandlePopupEnabled ) + { + mEventData->mDecorator->SetPopupActive( false ); + } } } + else + { + // Handle normal cursor move + ChangeState( EventData::EDITING ); + mEventData->mUpdateCursorPosition = true; + } - mEventData->mUpdateCursorPosition = true; mEventData->mUpdateInputStyle = true; mEventData->mScrollAfterUpdatePosition = true; } @@ -1227,11 +1427,15 @@ void Controller::Impl::OnTapEvent( const Event& event ) // Keep the tap 'x' position. Used to move the cursor. mEventData->mCursorHookPositionX = xPosition; + // Whether to touch point hits on a glyph. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::TAP, + matchedCharacter ); // When the cursor position is changing, delay cursor blinking mEventData->mDecorator->DelayCursorBlink(); @@ -1241,16 +1445,34 @@ void Controller::Impl::OnTapEvent( const Event& event ) mEventData->mPrimaryCursorPosition = 0u; } + // Update selection position after tapping + mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition; + mEventData->mUpdateCursorPosition = true; mEventData->mUpdateGrabHandlePosition = true; mEventData->mScrollAfterUpdatePosition = true; mEventData->mUpdateInputStyle = true; - // Notify the cursor position to the imf manager. - if( mEventData->mImfManager ) + // Notify the cursor position to the InputMethodContext. + if( mEventData->mInputMethodContext ) { - mEventData->mImfManager.SetCursorPosition( mEventData->mPrimaryCursorPosition ); - mEventData->mImfManager.NotifyCursorPosition(); + mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition ); + mEventData->mInputMethodContext.NotifyCursorPosition(); + } + } + else if( 2u == tapCount ) + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; + const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mDoubleTapAction ); } } } @@ -1323,12 +1545,26 @@ void Controller::Impl::OnLongPressEvent( const Event& event ) { DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" ); - if( EventData::EDITING == mEventData->mState ) + if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) ) { - ChangeState ( EventData::EDITING_WITH_POPUP ); + ChangeState( EventData::EDITING_WITH_POPUP ); mEventData->mDecoratorUpdated = true; mEventData->mUpdateInputStyle = true; } + else + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; + const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mLongPressAction ); + } + } } void Controller::Impl::OnHandleEvent( const Event& event ) @@ -1350,11 +1586,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; // Need to calculate the handle's new position. + bool matchedCharacter = false; const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); if( Event::GRAB_HANDLE_EVENT == event.type ) { @@ -1391,6 +1630,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = true; + mEventData->mIsRightHandleSelected = false; } else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { @@ -1411,6 +1651,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = true; } } // end ( HANDLE_PRESSED == state ) else if( ( HANDLE_RELEASED == state ) || @@ -1423,11 +1664,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x; const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y; + bool matchedCharacter = false; handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); } if( Event::GRAB_HANDLE_EVENT == event.type ) @@ -1539,11 +1783,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The grab handle's position is in decorator's coords. Need to transforms to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, position.x - mModel->mScrollPosition.x, - position.y - mModel->mScrollPosition.y ); + position.y - mModel->mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( mEventData->mPrimaryCursorPosition != handlePosition ) { @@ -1580,11 +1827,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The selection handle's position is in decorator's coords. Need to transform to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, position.x - mModel->mScrollPosition.x, - position.y - mModel->mScrollPosition.y ); + position.y - mModel->mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( leftSelectionHandleEvent ) { @@ -1637,7 +1887,8 @@ void Controller::Impl::OnSelectEvent( const Event& event ) // Calculates the logical position from the x,y coords. RepositionSelectionHandles( xPosition, - yPosition ); + yPosition, + Controller::NoTextTap::HIGHLIGHT ); } } @@ -1653,15 +1904,13 @@ void Controller::Impl::OnSelectAllEvent() if( mEventData->mSelectionEnabled ) { - ChangeState( EventData::SELECTING ); + // Calculates the logical position from the start. + RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x, + 0.f - mModel->mScrollPosition.y, + Controller::NoTextTap::HIGHLIGHT ); mEventData->mLeftSelectionPosition = 0u; mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count(); - - mEventData->mScrollAfterUpdatePosition = true; - mEventData->mUpdateLeftSelectionPosition = true; - mEventData->mUpdateRightSelectionPosition = true; - mEventData->mUpdateHighlightBox = true; } } @@ -1711,8 +1960,18 @@ void Controller::Impl::RetrieveSelection( std::string& selectedText, bool delete mModel->mLogicalModel->UpdateTextStyleRuns( startOfSelectedText, -static_cast( lengthOfSelectedText ) ); // Mark the paragraphs to be updated. - mTextUpdateInfo.mCharacterIndex = startOfSelectedText; - mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText; + if( Layout::Engine::SINGLE_LINE_BOX == mLayoutEngine.GetLayout() ) + { + mTextUpdateInfo.mCharacterIndex = 0; + mTextUpdateInfo.mNumberOfCharactersToRemove = mTextUpdateInfo.mPreviousNumberOfCharacters; + mTextUpdateInfo.mNumberOfCharactersToAdd = mTextUpdateInfo.mPreviousNumberOfCharacters - lengthOfSelectedText; + mTextUpdateInfo.mClearAll = true; + } + else + { + mTextUpdateInfo.mCharacterIndex = startOfSelectedText; + mTextUpdateInfo.mNumberOfCharactersToRemove = lengthOfSelectedText; + } // Delete text between handles Vector::Iterator first = utf32Characters.Begin() + startOfSelectedText; @@ -1783,6 +2042,8 @@ void Controller::Impl::RepositionSelectionHandles() if( selectionStart == selectionEnd ) { // Nothing to select if handles are in the same place. + // So, deactive Highlight box. + mEventData->mDecorator->SetHighlightActive( false ); return; } @@ -2134,7 +2395,7 @@ void Controller::Impl::RepositionSelectionHandles() const SelectionBoxInfo& firstSelectionBoxLineInfo = *( selectionBoxLinesInfo.Begin() ); highLightPosition.y = firstSelectionBoxLineInfo.lineOffset; - mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize ); + mEventData->mDecorator->SetHighLightBox( highLightPosition, highLightSize, static_cast( mModel->GetOutlineWidth() ) ); if( !mEventData->mDecorator->IsSmoothHandlePanEnabled() ) { @@ -2161,14 +2422,11 @@ void Controller::Impl::RepositionSelectionHandles() secondaryCursorInfo.lineHeight ); } - // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection - mEventData->mPrimaryCursorPosition = ( indicesSwapped ) ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition; - // Set the flag to update the decorator. mEventData->mDecoratorUpdated = true; } -void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) +void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ) { if( NULL == mEventData ) { @@ -2194,16 +2452,18 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY // Find which word was selected CharacterIndex selectionStart( 0 ); CharacterIndex selectionEnd( 0 ); - const bool indicesFound = FindSelectionIndices( mModel->mVisualModel, + CharacterIndex noTextHitIndex( 0 ); + const bool characterHit = FindSelectionIndices( mModel->mVisualModel, mModel->mLogicalModel, mMetrics, visualX, visualY, selectionStart, - selectionEnd ); + selectionEnd, + noTextHitIndex ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - if( indicesFound ) + if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) ) { ChangeState( EventData::SELECTING ); @@ -2214,20 +2474,33 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY mEventData->mUpdateRightSelectionPosition = true; mEventData->mUpdateHighlightBox = true; - // It may happen an IMF commit event arrives before the selection event - // if the IMF manager is in pre-edit state. The commit event will set the + // It may happen an InputMethodContext commit event arrives before the selection event + // if the InputMethodContext is in pre-edit state. The commit event will set the // mEventData->mUpdateCursorPosition flag to true. If it's not set back // to false, the highlight box won't be updated. mEventData->mUpdateCursorPosition = false; mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); + + // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection + mEventData->mPrimaryCursorPosition = std::max( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); } - else + else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action ) { // Nothing to select. i.e. a white space, out of bounds - ChangeState( EventData::EDITING ); + ChangeState( EventData::EDITING_WITH_POPUP ); - mEventData->mPrimaryCursorPosition = selectionEnd; + mEventData->mPrimaryCursorPosition = noTextHitIndex; + + mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateInputStyle = true; + } + else if( Controller::NoTextTap::NO_ACTION == action ) + { + // Nothing to select. i.e. a white space, out of bounds + mEventData->mPrimaryCursorPosition = noTextHitIndex; mEventData->mUpdateCursorPosition = true; mEventData->mUpdateGrabHandlePosition = true; @@ -2332,8 +2605,11 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); mEventData->mDecorator->StopCursorBlink(); mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + if ( mEventData->mGrabHandleEnabled ) + { + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + } mEventData->mDecorator->SetHighlightActive( true ); if( mEventData->mGrabHandlePopupEnabled ) { @@ -2377,7 +2653,7 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); mEventData->mDecorator->SetHighlightActive( false ); } - else + else if ( mEventData->mGrabHandleEnabled ) { mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); } @@ -2399,7 +2675,10 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->StartCursorBlink(); } // Grab handle is not shown until a tap is received whilst EDITING - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + 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 ); @@ -2415,8 +2694,11 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); mEventData->mDecorator->StopCursorBlink(); mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + if ( mEventData->mGrabHandleEnabled ) + { + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + } mEventData->mDecorator->SetHighlightActive( true ); if( mEventData->mGrabHandlePopupEnabled ) { @@ -2434,7 +2716,10 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->StartCursorBlink(); } - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + 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 ); @@ -2455,7 +2740,10 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->StartCursorBlink(); } - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + 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 ); @@ -2508,21 +2796,41 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, cursorInfo.lineHeight = GetDefaultFontLineHeight(); cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; + bool isRTL = false; + if( mModel->mMatchSystemLanguageDirection ) + { + isRTL = mLayoutDirection == LayoutDirection::RIGHT_TO_LEFT; + } + switch( mModel->mHorizontalAlignment ) { - case Layout::HORIZONTAL_ALIGN_BEGIN: + case Text::HorizontalAlignment::BEGIN : { - cursorInfo.primaryPosition.x = 0.f; + if( isRTL ) + { + cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); + } + else + { + cursorInfo.primaryPosition.x = 0.f; + } break; } - case Layout::HORIZONTAL_ALIGN_CENTER: + case Text::HorizontalAlignment::CENTER: { cursorInfo.primaryPosition.x = floorf( 0.5f * mModel->mVisualModel->mControlSize.width ); break; } - case Layout::HORIZONTAL_ALIGN_END: + case Text::HorizontalAlignment::END: { - cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); + if( isRTL ) + { + cursorInfo.primaryPosition.x = 0.f; + } + else + { + cursorInfo.primaryPosition.x = mModel->mVisualModel->mControlSize.width - mEventData->mDecorator->GetCursorWidth(); + } break; } } @@ -2531,13 +2839,25 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, return; } - Text::GetCursorPosition( mModel->mVisualModel, - mModel->mLogicalModel, - mMetrics, - logical, + const bool isMultiLine = ( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ); + GetCursorPositionParameters parameters; + parameters.visualModel = mModel->mVisualModel; + parameters.logicalModel = mModel->mLogicalModel; + parameters.metrics = mMetrics; + parameters.logical = logical; + parameters.isMultiline = isMultiLine; + + Text::GetCursorPosition( parameters, cursorInfo ); - if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ) + // Adds Outline offset. + const float outlineWidth = static_cast( mModel->GetOutlineWidth() ); + cursorInfo.primaryPosition.x += outlineWidth; + cursorInfo.primaryPosition.y += outlineWidth; + cursorInfo.secondaryPosition.x += outlineWidth; + cursorInfo.secondaryPosition.y += outlineWidth; + + if( isMultiLine ) { // If the text is editable and multi-line, the cursor position after a white space shouldn't exceed the boundaries of the text control. @@ -2618,6 +2938,8 @@ void Controller::Impl::UpdateCursorPosition( const CursorInfo& cursorInfo ) const Vector2 cursorPosition = cursorInfo.primaryPosition + mModel->mScrollPosition; + mEventData->mDecorator->SetGlyphOffset( PRIMARY_CURSOR, cursorInfo.glyphOffset ); + // Sets the cursor position. mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, cursorPosition.x, @@ -2753,13 +3075,16 @@ void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position, flo mModel->mScrollPosition.x = mModel->mVisualModel->mControlSize.width - positionEndX; } - if( decoratorPositionBeginY < 0.f ) - { - mModel->mScrollPosition.y = -position.y; - } - else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height ) + if( Layout::Engine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ) { - mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY; + if( decoratorPositionBeginY < 0.f ) + { + mModel->mScrollPosition.y = -position.y; + } + else if( decoratorPositionEndY > mModel->mVisualModel->mControlSize.height ) + { + mModel->mScrollPosition.y = mModel->mVisualModel->mControlSize.height - positionEndY; + } } } @@ -2768,9 +3093,20 @@ void Controller::Impl::ScrollTextToMatchCursor( const CursorInfo& cursorInfo ) // Get the current cursor position in decorator coords. const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); + const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( mEventData->mPrimaryCursorPosition ); + + + // Calculate the offset to match the cursor position before the character was deleted. mModel->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x; - mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset; + + //If text control has more than two lines and current line index is not last, calculate scrollpositionY + if( mModel->mVisualModel->mLines.Count() > 1u && lineIndex != mModel->mVisualModel->mLines.Count() -1u ) + { + const float currentCursorGlyphOffset = mEventData->mDecorator->GetGlyphOffset( PRIMARY_CURSOR ); + mModel->mScrollPosition.y = currentCursorPosition.y - cursorInfo.lineOffset - currentCursorGlyphOffset; + } + ClampHorizontalScroll( mModel->mVisualModel->GetLayoutSize() ); ClampVerticalScroll( mModel->mVisualModel->GetLayoutSize() ); @@ -2787,6 +3123,169 @@ void Controller::Impl::RequestRelayout() } } +Actor Controller::Impl::CreateBackgroundActor() +{ + // NOTE: Currently we only support background color for one line left-to-right text, + // so the following calculation is based on one line left-to-right text only! + + Actor actor; + + Length numberOfGlyphs = mView.GetNumberOfGlyphs(); + if( numberOfGlyphs > 0u ) + { + 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 ); + + const GlyphInfo* const glyphsBuffer = glyphs.Begin(); + const Vector2* const positionsBuffer = positions.Begin(); + + BackgroundMesh mesh; + mesh.mVertices.Reserve( 4u * glyphs.Size() ); + mesh.mIndices.Reserve( 6u * glyphs.Size() ); + + const Vector2 textSize = mView.GetLayoutSize(); + + const float offsetX = textSize.width * 0.5f; + const float offsetY = textSize.height * 0.5f; + + const Vector4* const backgroundColorsBuffer = mView.GetBackgroundColors(); + const ColorIndex* const backgroundColorIndicesBuffer = mView.GetBackgroundColorIndices(); + + Vector4 quad; + uint32_t numberOfQuads = 0u; + + for( uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i ) + { + 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 ColorIndex backgroundColorIndex = ( nullptr == backgroundColorsBuffer ) ? 0u : *( backgroundColorIndicesBuffer + i ); + const Vector4& backgroundColor = ( 0u == backgroundColorIndex ) ? Color::TRANSPARENT : *( backgroundColorsBuffer + backgroundColorIndex - 1u ); + + // Only create quads for glyphs with a background color + if ( backgroundColor != Color::TRANSPARENT ) + { + const Vector2 position = *( positionsBuffer + i ); + + if ( i == 0u && glyphSize == 1u ) // Only one glyph in the whole text + { + quad.x = position.x; + quad.y = 0.0f; + quad.z = quad.x + std::max( glyph.advance, glyph.xBearing + glyph.width ); + quad.w = textSize.height; + } + else if ( i == 0u ) // The first glyph in the whole text + { + quad.x = position.x; + quad.y = 0.0f; + quad.z = quad.x - glyph.xBearing + glyph.advance; + quad.w = textSize.height; + } + else if ( i == glyphSize - 1u ) // The last glyph in the whole text + { + quad.x = position.x - glyph.xBearing; + quad.y = 0.0f; + quad.z = quad.x + std::max( glyph.advance, glyph.xBearing + glyph.width ); + quad.w = textSize.height; + } + else // The glyph in the middle of the text + { + quad.x = position.x - glyph.xBearing; + quad.y = 0.0f; + quad.z = quad.x + glyph.advance; + quad.w = textSize.height; + } + + 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++; + } + } + + // 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; + + PropertyBuffer quadVertices = PropertyBuffer::New( quadVertexFormat ); + quadVertices.SetData( &mesh.mVertices[ 0 ], mesh.mVertices.Size() ); + + Geometry quadGeometry = Geometry::New(); + quadGeometry.AddVertexBuffer( quadVertices ); + quadGeometry.SetIndexBuffer( &mesh.mIndices[ 0 ], mesh.mIndices.Size() ); + + if( !mShaderBackground ) + { + mShaderBackground = Shader::New( VERTEX_SHADER_BACKGROUND, FRAGMENT_SHADER_BACKGROUND ); + } + + 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.SetName( "TextBackgroundColorActor" ); + actor.SetParentOrigin( ParentOrigin::TOP_LEFT ); + actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + actor.SetSize( textSize ); + actor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR ); + actor.AddRenderer( renderer ); + } + } + + return actor; +} + } // namespace Text } // namespace Toolkit