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.cpp;h=1e9ee8928697e1ed6dfa03b12f189ea1b20f06c2;hp=219d8e568765b8c6c2e4bfc219f584a0326fbf6e;hb=f4352109bea0752f4e514d67641e47c4d9744451;hpb=cc82bd9b187cda8fe2c8336b73fd1fa9376cfebd diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index 219d8e5..1e9ee89 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -44,7 +44,6 @@ namespace { const float MAX_FLOAT = std::numeric_limits::max(); -const std::string EMPTY_STRING; enum ModifyType { @@ -59,6 +58,8 @@ struct ModifyEvent std::string text; }; +const std::string EMPTY_STRING(""); + } // namespace namespace Dali @@ -70,6 +71,31 @@ namespace Toolkit namespace Text { +struct Controller::FontDefaults +{ + FontDefaults() + : mDefaultPointSize(0.0f), + mFontId(0u) + { + } + + FontId GetFontId( TextAbstraction::FontClient& fontClient ) + { + if( !mFontId ) + { + Dali::TextAbstraction::PointSize26Dot6 pointSize = mDefaultPointSize*64; + mFontId = fontClient.GetFontId( mDefaultFontFamily, mDefaultFontStyle, pointSize ); + } + + return mFontId; + } + + std::string mDefaultFontFamily; + std::string mDefaultFontStyle; + float mDefaultPointSize; + FontId mFontId; +}; + struct Controller::TextInput { // Used to queue input events until DoRelayout() @@ -105,6 +131,50 @@ struct Controller::TextInput Param p3; }; + struct CursorInfo + { + CursorInfo() + : primaryPosition(), + secondaryPosition(), + lineHeight( 0.f ), + primaryCursorHeight( 0.f ), + secondaryCursorHeight( 0.f ), + isSecondaryCursor( false ) + {} + + ~CursorInfo() + {} + + Vector2 primaryPosition; ///< The primary cursor's position. + Vector2 secondaryPosition; ///< The secondary cursor's position. + float lineHeight; ///< The height of the line where the cursor is placed. + float primaryCursorHeight; ///< The primary cursor's height. + float secondaryCursorHeight; ///< The secondary cursor's height. + bool isSecondaryCursor; ///< Whether the secondary cursor is valid. + }; + + /** + * @brief Some characters can be shaped in more than one glyph. + * This struct is used to retrieve metrics from these group of glyphs. + */ + struct GlyphMetrics + { + GlyphMetrics() + : fontHeight( 0.f ), + advance( 0.f ), + ascender( 0.f ), + xBearing( 0.f ) + {} + + ~GlyphMetrics() + {} + + float fontHeight; ///< The font's height of that glyphs. + float advance; ///< The sum of all the advances of all the glyphs. + float ascender; ///< The font's ascender. + float xBearing; ///< The x bearing of the first glyph. + }; + enum State { INACTIVE, @@ -115,10 +185,14 @@ struct Controller::TextInput TextInput( LogicalModelPtr logicalModel, VisualModelPtr visualModel, - DecoratorPtr decorator ) + DecoratorPtr decorator, + FontDefaults* fontDefaults, + TextAbstraction::FontClient& fontClient ) : mLogicalModel( logicalModel ), mVisualModel( visualModel ), mDecorator( decorator ), + mFontDefaults( fontDefaults ), + mFontClient( fontClient ), mState( INACTIVE ), mPrimaryCursorPosition( 0u ), mSecondaryCursorPosition( 0u ), @@ -130,8 +204,7 @@ struct Controller::TextInput mHorizontalScrollingEnabled( true ), mVerticalScrollingEnabled( false ), mUpdateCursorPosition( false ) - { - } + {} /** * @brief Helper to move the cursor, grab handle etc. @@ -211,11 +284,17 @@ struct Controller::TextInput if( Dali::DALI_KEY_CURSOR_LEFT == keyCode ) { - // TODO + if( mPrimaryCursorPosition > 0u ) + { + mPrimaryCursorPosition = CalculateNewCursorIndex( mPrimaryCursorPosition - 1u ); + } } else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) { - // TODO + if( mLogicalModel->GetNumberOfCharacters() > mPrimaryCursorPosition ) + { + mPrimaryCursorPosition = CalculateNewCursorIndex( mPrimaryCursorPosition ); + } } else if( Dali::DALI_KEY_CURSOR_UP == keyCode ) { @@ -225,6 +304,8 @@ struct Controller::TextInput { // TODO } + + UpdateCursorPosition(); } void HandleCursorKey( int keyCode ) @@ -243,12 +324,11 @@ struct Controller::TextInput float xPosition = event.p2.mFloat - alignmentOffset.x; float yPosition = event.p3.mFloat - alignmentOffset.y; - float height(0.0f); - GetClosestCursorPosition( mPrimaryCursorPosition, xPosition, yPosition, height ); - mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height ); - mUpdateCursorPosition = false; - mDecoratorUpdated = true; + mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, + yPosition ); + + UpdateCursorPosition(); } else if( mSelectionEnabled && 2u == tapCount ) @@ -257,7 +337,7 @@ struct Controller::TextInput RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat ); } - } + } void OnPanEvent( const Event& event, const Vector2& controlSize, @@ -319,16 +399,16 @@ struct Controller::TextInput if( GRAB_HANDLE_PRESSED == state ) { - float xPosition = event.p2.mFloat; - float yPosition = event.p3.mFloat; - float height(0.0f); + float xPosition = event.p2.mFloat + mScrollPosition.x; + float yPosition = event.p3.mFloat + mScrollPosition.y; - GetClosestCursorPosition( mPrimaryCursorPosition, xPosition, yPosition, height ); + mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, + yPosition ); + + UpdateCursorPosition(); - mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height ); //mDecorator->HidePopup(); ChangeState ( EDITING ); - mDecoratorUpdated = true; } else if ( mGrabHandlePopupEnabled && GRAB_HANDLE_RELEASED == state ) @@ -359,7 +439,7 @@ struct Controller::TextInput // TODO - multi-line selection const Vector& lines = mVisualModel->mLines; - float height = lines.Count() ? lines[0].lineSize.height : 0.0f; + float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f; mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE, primaryX, 0.0f, height ); mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, 0.0f, height ); @@ -431,14 +511,18 @@ struct Controller::TextInput } } - LineIndex GetClosestLine( float y ) + LineIndex GetClosestLine( float y ) const { - LineIndex lineIndex( 0u ); + float totalHeight = 0.f; + LineIndex lineIndex = 0u; const Vector& lines = mVisualModel->mLines; - for( float totalHeight = 0; lineIndex < lines.Count(); ++lineIndex ) + for( LineIndex endLine = lines.Count(); + lineIndex < endLine; + ++lineIndex ) { - totalHeight += lines[lineIndex].lineSize.height; + const LineRun& lineRun = lines[lineIndex]; + totalHeight += lineRun.ascender + -lineRun.descender; if( y < totalHeight ) { return lineIndex; @@ -448,14 +532,25 @@ struct Controller::TextInput return lineIndex-1; } - void GetClosestCursorPosition( CharacterIndex& logical, float& visualX, float& visualY, float& height ) + /** + * @brief Retrieves the cursor's logical position for a given touch point x,y + * + * @param[in] visualX The touch point x. + * @param[in] visualY The touch point y. + * + * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. + */ + CharacterIndex GetClosestCursorIndex( float visualX, + float visualY ) const { - Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); - Length numberOfLines = mVisualModel->mLines.Count(); + CharacterIndex logicalIndex = 0u; + + const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); + const Length numberOfLines = mVisualModel->mLines.Count(); if( 0 == numberOfGlyphs || 0 == numberOfLines ) { - return; + return logicalIndex; } // Transform to visual model coords @@ -463,117 +558,380 @@ struct Controller::TextInput visualY -= mScrollPosition.y; // Find which line is closest - LineIndex lineIndex( GetClosestLine( visualY ) ); - - const Vector& glyphs = mVisualModel->mGlyphs; - const GlyphInfo* const glyphsBuffer = glyphs.Begin(); + const LineIndex lineIndex = GetClosestLine( visualY ); + const LineRun& line = mVisualModel->mLines[lineIndex]; + // Get the positions of the glyphs. const Vector& positions = mVisualModel->mGlyphPositions; const Vector2* const positionsBuffer = positions.Begin(); - unsigned int closestGlyph = 0; - bool leftOfGlyph( false ); // which side of the glyph? - float closestDistance = MAX_FLOAT; + // Get the visual to logical conversion tables. + const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL; + const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin(); - const LineRun& line = mVisualModel->mLines[lineIndex]; - GlyphIndex startGlyph = line.glyphIndex; - GlyphIndex endGlyph = line.glyphIndex + line.numberOfGlyphs; - DALI_ASSERT_DEBUG( endGlyph <= glyphs.Count() && "Invalid line info" ); + // Get the character to glyph conversion table. + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + + // Get the glyphs per character table. + const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); + + // If the vector is void, there is no right to left characters. + const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer; - for( GlyphIndex i = startGlyph; i < endGlyph; ++i ) + const CharacterIndex startCharacter = line.characterRun.characterIndex; + const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters; + DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" ); + + // Whether there is a hit on a glyph. + bool matched = false; + + // Traverses glyphs in visual order. To do that use the visual to logical conversion table. + CharacterIndex visualIndex = startCharacter; + for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex ) { - const GlyphInfo& glyphInfo = *( glyphsBuffer + i ); - const Vector2& position = *( positionsBuffer + i ); - float glyphX = position.x + glyphInfo.width*0.5f; - float glyphY = position.y + glyphInfo.height*0.5f; + // The character in logical order. + const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex; + + // The first glyph for that character in logical order. + const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex ); - float distanceToGlyph = fabsf( glyphX - visualX ) + fabsf( glyphY - visualY ); + // The number of glyphs for that character + const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); - if( distanceToGlyph < closestDistance ) + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( glyphLogicalOrderIndex, + numberOfGlyphs, + glyphMetrics ); + + const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex ); + + const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance; + + if( visualX < glyphX ) { - closestDistance = distanceToGlyph; - closestGlyph = i; - leftOfGlyph = ( visualX < glyphX ); + matched = true; + break; } } - // Calculate the logical position - logical = mVisualModel->GetCharacterIndex( closestGlyph ); + // Return the logical position of the cursor in characters. - // Returns the visual position of the glyph - visualX = positions[closestGlyph].x; - if( !leftOfGlyph ) - { - visualX += glyphs[closestGlyph].width; - - //if( LTR ) TODO - ++logical; - } - else// if ( RTL ) TODO + if( !matched ) { - //++logical; + visualIndex = endCharacter; } - visualY = 0.0f; - height = line.lineSize.height; + return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex; } - void UpdateCursorPosition() + /** + * @brief Calculates the cursor's position for a given character index in the logical order. + * + * It retrieves as well the line's height and the cursor's height and + * if there is a valid alternative cursor, its position and height. + * + * @param[in] logical The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. + * @param[out] cursorInfo The line's height, the cursor's height, the cursor's position and whether there is an alternative cursor. + */ + void GetCursorPosition( CharacterIndex logical, + CursorInfo& cursorInfo ) const { - if( 0 == mVisualModel->mGlyphs.Count() ) + // TODO: Check for multiline with \n, etc... + + // Check if the logical position is the first or the last one of the text. + const bool isFirstPosition = 0u == logical; + const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical; + + if( isFirstPosition && isLastPosition ) { + // There is zero characters. Get the default font. + + FontId defaultFontId = 0u; + if( NULL == mFontDefaults ) + { + defaultFontId = mFontClient.GetFontId( EMPTY_STRING, + EMPTY_STRING ); + } + else + { + defaultFontId = mFontDefaults->GetFontId( mFontClient ); + } + + Text::FontMetrics fontMetrics; + mFontClient.GetFontMetrics( defaultFontId, fontMetrics ); + + cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender; + cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; + + cursorInfo.primaryPosition.x = 0.f; + cursorInfo.primaryPosition.y = 0.f; + + // Nothing else to do. return; } - // FIXME GetGlyphIndex() is behaving strangely -#if 0 - GlyphIndex cursorGlyph = mVisualModel->GetGlyphIndex( mPrimaryCursorPosition ); -#else - GlyphIndex cursorGlyph( 0u ); - for( cursorGlyph = 0; cursorGlyph < mVisualModel->mGlyphs.Count(); ++cursorGlyph ) + // Get the previous logical index. + const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u; + + // Decrease the logical index if it's the last one. + if( isLastPosition ) { - if( mPrimaryCursorPosition == mVisualModel->GetCharacterIndex( cursorGlyph ) ) - { - break; - } + --logical; + } + + // Get the direction of the character and the previous one. + const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL; + + CharacterDirection isCurrentRightToLeft = false; + CharacterDirection isPreviousRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical ); + isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical ); } -#endif - float visualX( 0.0f ); - float visualY( 0.0f ); - float height( 0.0f ); - const Vector& lineRuns = mVisualModel->mLines; + // Get the line where the character is laid-out. + const LineRun* modelLines = mVisualModel->mLines.Begin(); + + const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical ); + const LineRun& line = *( modelLines + lineIndex ); + + // Get the paragraph's direction. + const CharacterDirection isRightToLeftParagraph = line.direction; + + // Check whether there is an alternative position: - if( cursorGlyph > 0 ) + cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) || + ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); + + // Set the line height. + cursorInfo.lineHeight = line.ascender + -line.descender; + + // Convert the cursor position into the glyph position. + CharacterIndex characterIndex = logical; + if( cursorInfo.isSecondaryCursor && + ( isRightToLeftParagraph != isCurrentRightToLeft ) ) { - --cursorGlyph; + characterIndex = previousLogical; + } - visualX = mVisualModel->mGlyphPositions[ cursorGlyph ].x; - //if( LTR ) TODO - visualX += mVisualModel->mGlyphs[ cursorGlyph ].width; + const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); + const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); + const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex ); - // Find the line height - GlyphIndex lastGlyph( 0 ); - for( LineIndex lineIndex = 0u; lineIndex < lineRuns.Count(); ++lineIndex ) + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( currentGlyphIndex, + numberOfGlyphs, + glyphMetrics ); + + float interGlyphAdvance = 0.f; + if( !isLastPosition && + ( numberOfCharacters > 1u ) ) + { + const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex ); + interGlyphAdvance = static_cast( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast( numberOfCharacters ); + } + + // Get the glyph position and x bearing. + const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex ); + + // Set the cursor's height. + cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight; + + // Set the position. + cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance ); + cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; + + if( isLastPosition ) + { + // The position of the cursor after the last character needs special + // care depending on its direction and the direction of the paragraph. + + if( cursorInfo.isSecondaryCursor ) { - lastGlyph = (lineRuns[lineIndex].glyphIndex + lineRuns[lineIndex].numberOfGlyphs); - if( cursorGlyph < lastGlyph ) + // Need to find the first character after the last character with the paragraph's direction. + // i.e l0 l1 l2 r0 r1 should find r0. + + // TODO: check for more than one line! + characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; + characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex ); + + const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); + const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); + + const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex ); + + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( glyphIndex, + numberOfGlyphs, + glyphMetrics ); + + cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance ); + + cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; + } + else + { + if( !isCurrentRightToLeft ) + { + cursorInfo.primaryPosition.x += glyphMetrics.advance; + } + else { - height = lineRuns[lineIndex].lineSize.height; - break; + cursorInfo.primaryPosition.x -= glyphMetrics.advance; } } } - mDecorator->SetPosition( PRIMARY_CURSOR, visualX, visualY, height ); - mDecoratorUpdated = true; + // Set the alternative cursor position. + if( cursorInfo.isSecondaryCursor ) + { + // Convert the cursor position into the glyph position. + const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical ); + const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex ); + const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex ); + + // Get the glyph position. + const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex ); + + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( previousGlyphIndex, + numberOfGlyphs, + glyphMetrics ); + + // Set the cursor position and height. + cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) || + ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f ); + + cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; + + cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender ); + + // Update the primary cursor height as well. + cursorInfo.primaryCursorHeight *= 0.5f; + } } - LogicalModelPtr mLogicalModel; - VisualModelPtr mVisualModel; - DecoratorPtr mDecorator; + /** + * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character. + * + * @param[in] glyphIndex The index to the first glyph. + * @param[in] numberOfGlyphs The number of glyphs. + * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing). + */ + void GetGlyphsMetrics( GlyphIndex glyphIndex, + Length numberOfGlyphs, + GlyphMetrics& glyphMetrics ) const + { + const GlyphInfo* glyphsBuffer = mVisualModel->mGlyphs.Begin(); + + const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex ); + + Text::FontMetrics fontMetrics; + mFontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics ); - std::string mPlaceholderText; + glyphMetrics.fontHeight = fontMetrics.height; + glyphMetrics.advance = firstGlyph.advance; + glyphMetrics.ascender = fontMetrics.ascender; + glyphMetrics.xBearing = firstGlyph.xBearing; + + for( unsigned int i = 1u; i < numberOfGlyphs; ++i ) + { + const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i ); + + glyphMetrics.advance += glyphInfo.advance; + } + } + + /** + * @brief Calculates the new cursor index. + * + * It takes into account that in some scripts multiple characters can form a glyph and all of them + * need to be jumped with one key event. + * + * @param[in] index The initial new index. + * + * @return The new cursor index. + */ + CharacterIndex CalculateNewCursorIndex( CharacterIndex index ) const + { + CharacterIndex cursorIndex = mPrimaryCursorPosition; + + const Script script = mLogicalModel->GetScript( index ); + const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + + Length numberOfCharacters = 0u; + if( TextAbstraction::LATIN == script ) + { + // Prevents to jump the whole Latin ligatures like fi, ff, ... + numberOfCharacters = 1u; + } + else + { + GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); + numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); + + while( 0u == numberOfCharacters ) + { + numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); + ++glyphIndex; + } + } + + if( index < mPrimaryCursorPosition ) + { + cursorIndex -= numberOfCharacters; + } + else + { + cursorIndex += numberOfCharacters; + } + + return cursorIndex; + } + + void UpdateCursorPosition() + { + CursorInfo cursorInfo; + + GetCursorPosition( mPrimaryCursorPosition, + cursorInfo ); + + mDecorator->SetPosition( PRIMARY_CURSOR, + cursorInfo.primaryPosition.x, + cursorInfo.primaryPosition.y, + cursorInfo.primaryCursorHeight, + cursorInfo.lineHeight ); + + if( cursorInfo.isSecondaryCursor ) + { + mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); + mDecorator->SetPosition( SECONDARY_CURSOR, + cursorInfo.secondaryPosition.x, + cursorInfo.secondaryPosition.y, + cursorInfo.secondaryCursorHeight, + cursorInfo.lineHeight ); + } + else + { + mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + } + + mUpdateCursorPosition = false; + mDecoratorUpdated = true; + } + + LogicalModelPtr mLogicalModel; + VisualModelPtr mVisualModel; + DecoratorPtr mDecorator; + FontDefaults* mFontDefaults; + TextAbstraction::FontClient& mFontClient; + std::string mPlaceholderText; /** * This is used to delay handling events until after the model has been updated. @@ -602,31 +960,6 @@ struct Controller::TextInput bool mUpdateCursorPosition : 1; ///< True if the visual position of the cursor must be recalculated }; -struct Controller::FontDefaults -{ - FontDefaults() - : mDefaultPointSize(0.0f), - mFontId(0u) - { - } - - FontId GetFontId( TextAbstraction::FontClient& fontClient ) - { - if( !mFontId ) - { - Dali::TextAbstraction::PointSize26Dot6 pointSize = mDefaultPointSize*64; - mFontId = fontClient.GetFontId( mDefaultFontFamily, mDefaultFontStyle, pointSize ); - } - - return mFontId; - } - - std::string mDefaultFontFamily; - std::string mDefaultFontStyle; - float mDefaultPointSize; - FontId mFontId; -}; - struct Controller::Impl { Impl( ControlInterface& controlInterface ) @@ -651,9 +984,11 @@ struct Controller::Impl mView.SetVisualModel( mVisualModel ); - // Set the shadow properties to default + // Set the text properties to default + mVisualModel->SetTextColor( Color::WHITE ); mVisualModel->SetShadowOffset( Vector2::ZERO ); mVisualModel->SetShadowColor( Vector4::ZERO ); + mVisualModel->SetUnderlineEnabled( false ); } ~Impl() @@ -751,6 +1086,7 @@ void Controller::SetDefaultFontFamily( const std::string& defaultFontFamily ) mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); RequestRelayout(); } @@ -786,6 +1122,7 @@ void Controller::SetDefaultFontStyle( const std::string& defaultFontStyle ) mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); RequestRelayout(); } @@ -821,6 +1158,7 @@ void Controller::SetDefaultPointSize( float pointSize ) mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); RequestRelayout(); } @@ -835,7 +1173,7 @@ float Controller::GetDefaultPointSize() const return 0.0f; } -void Controller::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) +void Controller::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) const { if( mImpl->mFontDefaults ) { @@ -849,6 +1187,11 @@ void Controller::GetDefaultFonts( Vector& fonts, Length numberOfCharact } } +const Vector4& Controller::GetTextColor() const +{ + return mImpl->mVisualModel->GetTextColor(); +} + const Vector2& Controller::GetShadowOffset() const { return mImpl->mVisualModel->GetShadowOffset(); @@ -859,6 +1202,21 @@ const Vector4& Controller::GetShadowColor() const return mImpl->mVisualModel->GetShadowColor(); } +const Vector4& Controller::GetUnderlineColor() const +{ + return mImpl->mVisualModel->GetUnderlineColor(); +} + +bool Controller::IsUnderlineEnabled() const +{ + return mImpl->mVisualModel->IsUnderlineEnabled(); +} + +void Controller::SetTextColor( const Vector4& textColor ) +{ + mImpl->mVisualModel->SetTextColor( textColor ); +} + void Controller::SetShadowOffset( const Vector2& shadowOffset ) { mImpl->mVisualModel->SetShadowOffset( shadowOffset ); @@ -869,11 +1227,25 @@ void Controller::SetShadowColor( const Vector4& shadowColor ) mImpl->mVisualModel->SetShadowColor( shadowColor ); } +void Controller::SetUnderlineColor( const Vector4& color ) +{ + mImpl->mVisualModel->SetUnderlineColor( color ); +} + +void Controller::SetUnderlineEnabled( bool enabled ) +{ + mImpl->mVisualModel->SetUnderlineEnabled( enabled ); +} + void Controller::EnableTextInput( DecoratorPtr decorator ) { if( !mImpl->mTextInput ) { - mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, mImpl->mVisualModel, decorator ); + mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, + mImpl->mVisualModel, + decorator, + mImpl->mFontDefaults, + mImpl->mFontClient ); } } @@ -1111,6 +1483,7 @@ void Controller::ReplaceTextEvent( const std::string& text ) mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); // Convert text into UTF-32 Vector& utf32Characters = mImpl->mLogicalModel->mText; @@ -1164,6 +1537,7 @@ void Controller::InsertTextEvent( const std::string& text ) mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); // Convert text into UTF-32 Vector utf32Characters; @@ -1229,6 +1603,7 @@ void Controller::DeleteTextEvent() mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); mImpl->mVisualModel->mGlyphPositions.Clear(); mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); // Delte at current cursor position Vector& modifyText = mImpl->mLogicalModel->mText; @@ -1421,8 +1796,15 @@ bool Controller::DoRelayout( const Size& size, Length numberOfGlyphs = mImpl->mVisualModel->GetNumberOfGlyphs(); + if( 0u == numberOfGlyphs ) + { + // Nothing else to do if there is no glyphs. + return true; + } + Vector& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo; Vector& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo; + Vector& characterDirection = mImpl->mLogicalModel->mCharacterDirections; Vector& glyphs = mImpl->mVisualModel->mGlyphs; Vector& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters; Vector& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph; @@ -1432,6 +1814,7 @@ bool Controller::DoRelayout( const Size& size, mImpl->mLogicalModel->mText.Begin(), lineBreakInfo.Begin(), wordBreakInfo.Begin(), + ( 0u != characterDirection.Count() ) ? characterDirection.Begin() : NULL, numberOfGlyphs, glyphs.Begin(), glyphsToCharactersMap.Begin(),