+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f\n", this, size.width, size.height );
+
+ if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
+ {
+ bool glyphsRemoved( false );
+ if( 0u != mImpl->mVisualModel->GetNumberOfGlyphPositions() )
+ {
+ mImpl->mVisualModel->SetGlyphPositions( NULL, 0u );
+ glyphsRemoved = true;
+ }
+ // Not worth to relayout if width or height is equal to zero.
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout (skipped)\n" );
+ return glyphsRemoved;
+ }
+
+ if( size != mImpl->mControlSize )
+ {
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", mImpl->mControlSize.width, mImpl->mControlSize.height );
+
+ // Operations that need to be done if the size changes.
+ mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
+ LAYOUT |
+ ALIGN |
+ UPDATE_ACTUAL_SIZE |
+ REORDER );
+
+ mImpl->mControlSize = size;
+ }
+
+ // Make sure the model is up-to-date before layouting
+ ProcessModifyEvents();
+ mImpl->UpdateModel( mImpl->mOperationsPending );
+
+ Size layoutSize;
+ bool updated = DoRelayout( mImpl->mControlSize,
+ mImpl->mOperationsPending,
+ layoutSize );
+
+ // Do not re-do any operation until something changes.
+ mImpl->mOperationsPending = NO_OPERATION;
+
+ // After doing the text layout, the alignment offset to place the actor in the desired position can be calculated.
+ CalculateTextAlignment( size );
+
+ if( mImpl->mEventData )
+ {
+ // Move the cursor, grab handle etc.
+ updated = mImpl->ProcessInputEvents() || updated;
+ }
+
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::Relayout\n" );
+ return updated;
+}
+
+void Controller::ProcessModifyEvents()
+{
+ std::vector<ModifyEvent>& events = mImpl->mModifyEvents;
+
+ for( unsigned int i=0; i<events.size(); ++i )
+ {
+ if( ModifyEvent::TEXT_REPLACED == events[0].type )
+ {
+ // A (single) replace event should come first, otherwise we wasted time processing NOOP events
+ DALI_ASSERT_DEBUG( 0 == i && "Unexpected TEXT_REPLACED event" );
+
+ TextReplacedEvent();
+ }
+ else if( ModifyEvent::TEXT_INSERTED == events[0].type )
+ {
+ TextInsertedEvent();
+ }
+ else if( ModifyEvent::TEXT_DELETED == events[0].type )
+ {
+ // Placeholder-text cannot be deleted
+ if( !mImpl->IsShowingPlaceholderText() )
+ {
+ TextDeletedEvent();
+ }
+ }
+ }
+
+ // Discard temporary text
+ events.clear();
+}
+
+void Controller::ResetText()
+{
+ // Reset buffers.
+ mImpl->mLogicalModel->mText.Clear();
+ ClearModelData();
+
+ // Reset the cursor position
+ if( mImpl->mEventData )
+ {
+ mImpl->mEventData->mPrimaryCursorPosition = 0;
+ }
+
+ // We have cleared everything including the placeholder-text
+ mImpl->PlaceholderCleared();
+
+ // The natural size needs to be re-calculated.
+ mImpl->mRecalculateNaturalSize = true;
+
+ // Apply modifications to the model
+ mImpl->mOperationsPending = ALL_OPERATIONS;
+}
+
+void Controller::TextReplacedEvent()
+{
+ // Reset buffers.
+ ClearModelData();
+
+ // The natural size needs to be re-calculated.
+ mImpl->mRecalculateNaturalSize = true;
+
+ // Apply modifications to the model
+ mImpl->mOperationsPending = ALL_OPERATIONS;
+ mImpl->UpdateModel( ALL_OPERATIONS );
+ mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
+ ALIGN |
+ UPDATE_ACTUAL_SIZE |
+ REORDER );
+}
+
+void Controller::TextInsertedEvent()
+{
+ DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextInsertedEvent" );
+
+ // TODO - Optimize this
+ ClearModelData();
+
+ // The natural size needs to be re-calculated.
+ mImpl->mRecalculateNaturalSize = true;
+
+ // Apply modifications to the model; TODO - Optimize this
+ mImpl->mOperationsPending = ALL_OPERATIONS;
+ mImpl->UpdateModel( ALL_OPERATIONS );
+ mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
+ ALIGN |
+ UPDATE_ACTUAL_SIZE |
+ REORDER );
+
+ // Queue a cursor reposition event; this must wait until after DoRelayout()
+ mImpl->mEventData->mUpdateCursorPosition = true;
+ mImpl->mEventData->mScrollAfterUpdateCursorPosition = true;
+}
+
+void Controller::TextDeletedEvent()
+{
+ DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextDeletedEvent" );
+
+ // TODO - Optimize this
+ ClearModelData();
+
+ // The natural size needs to be re-calculated.
+ mImpl->mRecalculateNaturalSize = true;
+
+ // Apply modifications to the model; TODO - Optimize this
+ mImpl->mOperationsPending = ALL_OPERATIONS;
+ mImpl->UpdateModel( ALL_OPERATIONS );
+ mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
+ ALIGN |
+ UPDATE_ACTUAL_SIZE |
+ REORDER );
+
+ // Queue a cursor reposition event; this must wait until after DoRelayout()
+ mImpl->mEventData->mUpdateCursorPosition = true;
+ mImpl->mEventData->mScrollAfterUpdateCursorPosition = true;
+}
+
+bool Controller::DoRelayout( const Size& size,
+ OperationsMask operationsRequired,
+ Size& layoutSize )
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::DoRelayout %p size %f,%f\n", this, size.width, size.height );
+ bool viewUpdated( false );
+
+ // Calculate the operations to be done.
+ const OperationsMask operations = static_cast<OperationsMask>( mImpl->mOperationsPending & operationsRequired );
+
+ if( LAYOUT & operations )
+ {
+ // Some vectors with data needed to layout and reorder may be void
+ // after the first time the text has been laid out.
+ // Fill the vectors again.
+
+ Length numberOfGlyphs = mImpl->mVisualModel->GetNumberOfGlyphs();
+
+ if( 0u == numberOfGlyphs )
+ {
+ // Nothing else to do if there is no glyphs.
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::DoRelayout no glyphs, view updated true\n" );
+ return true;
+ }
+
+ Vector<LineBreakInfo>& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo;
+ Vector<WordBreakInfo>& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo;
+ Vector<CharacterDirection>& characterDirection = mImpl->mLogicalModel->mCharacterDirections;
+ Vector<GlyphInfo>& glyphs = mImpl->mVisualModel->mGlyphs;
+ Vector<CharacterIndex>& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters;
+ Vector<Length>& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph;
+
+ // Set the layout parameters.
+ LayoutParameters layoutParameters( size,
+ mImpl->mLogicalModel->mText.Begin(),
+ lineBreakInfo.Begin(),
+ wordBreakInfo.Begin(),
+ ( 0u != characterDirection.Count() ) ? characterDirection.Begin() : NULL,
+ numberOfGlyphs,
+ glyphs.Begin(),
+ glyphsToCharactersMap.Begin(),
+ charactersPerGlyph.Begin() );
+
+ // The laid-out lines.
+ // It's not possible to know in how many lines the text is going to be laid-out,
+ // but it can be resized at least with the number of 'paragraphs' to avoid
+ // some re-allocations.
+ Vector<LineRun>& lines = mImpl->mVisualModel->mLines;
+
+ // Delete any previous laid out lines before setting the new ones.
+ lines.Clear();
+
+ // The capacity of the bidirectional paragraph info is the number of paragraphs.
+ lines.Reserve( mImpl->mLogicalModel->mBidirectionalParagraphInfo.Capacity() );
+
+ // Resize the vector of positions to have the same size than the vector of glyphs.
+ Vector<Vector2>& glyphPositions = mImpl->mVisualModel->mGlyphPositions;
+ glyphPositions.Resize( numberOfGlyphs );
+
+ // Update the visual model.
+ viewUpdated = mImpl->mLayoutEngine.LayoutText( layoutParameters,
+ glyphPositions,
+ lines,
+ layoutSize );
+
+ if( viewUpdated )
+ {
+ // Reorder the lines
+ if( REORDER & operations )
+ {
+ Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mImpl->mLogicalModel->mBidirectionalParagraphInfo;
+
+ // Check first if there are paragraphs with bidirectional info.
+ if( 0u != bidirectionalInfo.Count() )
+ {
+ // Get the lines
+ const Length numberOfLines = mImpl->mVisualModel->GetNumberOfLines();
+
+ // Reorder the lines.
+ Vector<BidirectionalLineInfoRun> lineBidirectionalInfoRuns;
+ lineBidirectionalInfoRuns.Reserve( numberOfLines ); // Reserve because is not known yet how many lines have right to left characters.
+ ReorderLines( bidirectionalInfo,
+ lines,
+ lineBidirectionalInfoRuns );
+
+ // Set the bidirectional info into the model.
+ const Length numberOfBidirectionalInfoRuns = lineBidirectionalInfoRuns.Count();
+ mImpl->mLogicalModel->SetVisualToLogicalMap( lineBidirectionalInfoRuns.Begin(),
+ numberOfBidirectionalInfoRuns );
+
+ // Set the bidirectional info per line into the layout parameters.
+ layoutParameters.lineBidirectionalInfoRunsBuffer = lineBidirectionalInfoRuns.Begin();
+ layoutParameters.numberOfBidirectionalInfoRuns = numberOfBidirectionalInfoRuns;
+
+ // Get the character to glyph conversion table and set into the layout.
+ layoutParameters.charactersToGlyphsBuffer = mImpl->mVisualModel->mCharactersToGlyph.Begin();
+
+ // Get the glyphs per character table and set into the layout.
+ layoutParameters.glyphsPerCharacterBuffer = mImpl->mVisualModel->mGlyphsPerCharacter.Begin();
+
+ // Re-layout the text. Reorder those lines with right to left characters.
+ mImpl->mLayoutEngine.ReLayoutRightToLeftLines( layoutParameters,
+ glyphPositions );
+
+ // Free the allocated memory used to store the conversion table in the bidirectional line info run.
+ for( Vector<BidirectionalLineInfoRun>::Iterator it = lineBidirectionalInfoRuns.Begin(),
+ endIt = lineBidirectionalInfoRuns.End();
+ it != endIt;
+ ++it )
+ {
+ BidirectionalLineInfoRun& bidiLineInfo = *it;
+
+ free( bidiLineInfo.visualToLogicalMap );
+ }
+ }
+ } // REORDER
+
+ if( ALIGN & operations )
+ {
+ mImpl->mLayoutEngine.Align( layoutParameters,
+ layoutSize,
+ lines,
+ glyphPositions );
+ }
+
+ // Sets the actual size.
+ if( UPDATE_ACTUAL_SIZE & operations )
+ {
+ mImpl->mVisualModel->SetActualSize( layoutSize );
+ }
+ } // view updated
+ }
+ else
+ {
+ layoutSize = mImpl->mVisualModel->GetActualSize();
+ }
+
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::DoRelayout, view updated %s\n", ( viewUpdated ? "true" : "false" ) );
+ return viewUpdated;
+}
+
+void Controller::CalculateTextAlignment( const Size& size )
+{
+ // Get the direction of the first character.
+ const CharacterDirection firstParagraphDirection = mImpl->mLogicalModel->GetCharacterDirection( 0u );
+
+ const Size& actualSize = mImpl->mVisualModel->GetActualSize();
+
+ // If the first paragraph is right to left swap ALIGN_BEGIN and ALIGN_END;
+ LayoutEngine::HorizontalAlignment horizontalAlignment = mImpl->mLayoutEngine.GetHorizontalAlignment();
+ if( firstParagraphDirection &&
+ ( LayoutEngine::HORIZONTAL_ALIGN_CENTER != horizontalAlignment ) )
+ {
+ if( LayoutEngine::HORIZONTAL_ALIGN_BEGIN == horizontalAlignment )
+ {
+ horizontalAlignment = LayoutEngine::HORIZONTAL_ALIGN_END;
+ }
+ else
+ {
+ horizontalAlignment = LayoutEngine::HORIZONTAL_ALIGN_BEGIN;
+ }
+ }
+
+ switch( horizontalAlignment )
+ {
+ case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
+ {
+ mImpl->mAlignmentOffset.x = 0.f;
+ break;
+ }
+ case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
+ {
+ const int intOffset = static_cast<int>( 0.5f * ( size.width - actualSize.width ) ); // try to avoid pixel alignment.
+ mImpl->mAlignmentOffset.x = static_cast<float>( intOffset );
+ break;
+ }
+ case LayoutEngine::HORIZONTAL_ALIGN_END:
+ {
+ mImpl->mAlignmentOffset.x = size.width - actualSize.width;
+ break;
+ }
+ }
+
+ const LayoutEngine::VerticalAlignment verticalAlignment = mImpl->mLayoutEngine.GetVerticalAlignment();
+ switch( verticalAlignment )
+ {
+ case LayoutEngine::VERTICAL_ALIGN_TOP:
+ {
+ mImpl->mAlignmentOffset.y = 0.f;
+ break;
+ }
+ case LayoutEngine::VERTICAL_ALIGN_CENTER:
+ {
+ const int intOffset = static_cast<int>( 0.5f * ( size.height - actualSize.height ) ); // try to avoid pixel alignment.
+ mImpl->mAlignmentOffset.y = static_cast<float>( intOffset );
+ break;
+ }
+ case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
+ {
+ mImpl->mAlignmentOffset.y = size.height - actualSize.height;
+ break;
+ }
+ }