+ // 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<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( 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 )
+ {
+ // 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
+ {
+ cursorInfo.primaryPosition.x -= glyphMetrics.advance;
+ }
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ /**
+ * @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 );
+
+ 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.
+ * The number of updates to the model is minimized to improve performance.
+ */
+ vector<Event> mEventQueue; ///< The queue of touch events etc.
+
+ State mState; ///< Selection mode, edit mode etc.
+
+ CharacterIndex mPrimaryCursorPosition; ///< Index into logical model for primary cursor
+ CharacterIndex mSecondaryCursorPosition; ///< Index into logical model for secondary cursor
+
+ /**
+ * 0,0 means that the top-left corner of the layout matches the top-left corner of the UI control.
+ * Typically this will have a negative value with scrolling occurs.
+ */
+ Vector2 mScrollPosition; ///< The text is offset by this position when scrolling.
+
+ bool mDecoratorUpdated : 1; ///< True if the decorator was updated during event processing
+ bool mCursorBlinkEnabled : 1; ///< True if cursor should blink when active
+ bool mGrabHandleEnabled : 1; ///< True if grab handle is enabled
+ bool mGrabHandlePopupEnabled : 1; ///< True if the grab handle popu-up should be shown
+ bool mSelectionEnabled : 1; ///< True if selection handles, highlight etc. are enabled
+ bool mHorizontalScrollingEnabled : 1; ///< True if horizontal scrolling is enabled
+ bool mVerticalScrollingEnabled : 1; ///< True if vertical scrolling is enabled
+ bool mUpdateCursorPosition : 1; ///< True if the visual position of the cursor must be recalculated
+};
+
+struct Controller::Impl
+{
+ Impl( ControlInterface& controlInterface )
+ : mControlInterface( controlInterface ),
+ mLogicalModel(),
+ mVisualModel(),
+ mFontDefaults( NULL ),
+ mTextInput( NULL ),
+ mFontClient(),
+ mView(),
+ mLayoutEngine(),
+ mModifyEvents(),
+ mControlSize(),
+ mAlignmentOffset(),
+ mOperationsPending( NO_OPERATION ),
+ mRecalculateNaturalSize( true )
+ {
+ mLogicalModel = LogicalModel::New();
+ mVisualModel = VisualModel::New();
+
+ mFontClient = TextAbstraction::FontClient::Get();
+
+ mView.SetVisualModel( mVisualModel );
+
+ // Set the text properties to default
+ mVisualModel->SetTextColor( Color::WHITE );
+ mVisualModel->SetShadowOffset( Vector2::ZERO );
+ mVisualModel->SetShadowColor( Vector4::ZERO );
+ mVisualModel->SetUnderlineEnabled( false );
+ mVisualModel->SetUnderlineHeight( 0.0f );
+ }
+
+ ~Impl()
+ {
+ delete mTextInput;
+ }
+
+ ControlInterface& mControlInterface; ///< Reference to the text controller.
+ LogicalModelPtr mLogicalModel; ///< Pointer to the logical model.
+ VisualModelPtr mVisualModel; ///< Pointer to the visual model.
+ FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font.
+ Controller::TextInput* mTextInput; ///< Avoid allocating everything for text input until EnableTextInput().
+ TextAbstraction::FontClient mFontClient; ///< Handle to the font client.
+ View mView; ///< The view interface to the rendering back-end.
+ LayoutEngine mLayoutEngine; ///< The layout engine.
+ std::vector<ModifyEvent> mModifyEvents; ///< Temporary stores the text set until the next relayout.
+ Size mControlSize; ///< The size of the control.
+ Vector2 mAlignmentOffset; ///< Vertical and horizontal offset of the whole text inside the control due to alignment.
+ OperationsMask mOperationsPending; ///< Operations pending to be done to layout the text.
+ bool mRecalculateNaturalSize:1; ///< Whether the natural size needs to be recalculated.
+};
+
+ControllerPtr Controller::New( ControlInterface& controlInterface )
+{
+ return ControllerPtr( new Controller( controlInterface ) );
+}
+
+void Controller::SetText( const std::string& text )
+{
+ // Cancel previously queued inserts etc.
+ mImpl->mModifyEvents.clear();
+
+ // Keep until size negotiation
+ ModifyEvent event;
+ event.type = REPLACE_TEXT;
+ event.text = text;
+ mImpl->mModifyEvents.push_back( event );