2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller.h>
24 #include <dali/public-api/adaptor-framework/key.h>
25 #include <dali/public-api/text-abstraction/font-client.h>
28 #include <dali-toolkit/internal/text/bidirectional-support.h>
29 #include <dali-toolkit/internal/text/character-set-conversion.h>
30 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
31 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
32 #include <dali-toolkit/internal/text/logical-model-impl.h>
33 #include <dali-toolkit/internal/text/multi-language-support.h>
34 #include <dali-toolkit/internal/text/script-run.h>
35 #include <dali-toolkit/internal/text/segmentation.h>
36 #include <dali-toolkit/internal/text/shaper.h>
37 #include <dali-toolkit/internal/text/text-io.h>
38 #include <dali-toolkit/internal/text/text-view.h>
39 #include <dali-toolkit/internal/text/visual-model-impl.h>
46 const float MAX_FLOAT = std::numeric_limits<float>::max();
47 const std::string EMPTY_STRING;
51 REPLACE_TEXT, ///< Replace the entire text
52 INSERT_TEXT, ///< Insert characters at the current cursor position
53 DELETE_TEXT ///< Delete a character at the current cursor position
73 struct Controller::TextInput
75 // Used to queue input events until DoRelayout()
78 KEYBOARD_FOCUS_GAIN_EVENT,
79 KEYBOARD_FOCUS_LOST_EVENT,
95 Event( EventType eventType )
115 TextInput( LogicalModelPtr logicalModel,
116 VisualModelPtr visualModel,
117 DecoratorPtr decorator )
118 : mLogicalModel( logicalModel ),
119 mVisualModel( visualModel ),
120 mDecorator( decorator ),
122 mPrimaryCursorPosition( 0u ),
123 mSecondaryCursorPosition( 0u ),
124 mDecoratorUpdated( false ),
125 mCursorBlinkEnabled( true ),
126 mGrabHandleEnabled( false ),
127 mGrabHandlePopupEnabled( false ),
128 mSelectionEnabled( false ),
129 mHorizontalScrollingEnabled( true ),
130 mVerticalScrollingEnabled( false ),
131 mUpdateCursorPosition( false )
136 * @brief Helper to move the cursor, grab handle etc.
138 bool ProcessInputEvents( const Vector2& controlSize,
139 const Vector2& alignmentOffset )
141 mDecoratorUpdated = false;
145 for( vector<TextInput::Event>::iterator iter = mEventQueue.begin(); iter != mEventQueue.end(); ++iter )
149 case KEYBOARD_FOCUS_GAIN_EVENT:
151 OnKeyboardFocus( true );
154 case KEYBOARD_FOCUS_LOST_EVENT:
156 OnKeyboardFocus( false );
159 case CURSOR_KEY_EVENT:
161 OnCursorKeyEvent( *iter );
166 OnTapEvent( *iter, alignmentOffset );
171 OnPanEvent( *iter, controlSize, alignmentOffset );
174 case GRAB_HANDLE_EVENT:
176 OnGrabHandleEvent( *iter );
183 // The cursor must also be repositioned after inserts into the model
184 if( mUpdateCursorPosition )
186 UpdateCursorPosition();
187 mUpdateCursorPosition = false;
192 return mDecoratorUpdated;
195 void OnKeyboardFocus( bool hasFocus )
199 ChangeState( INACTIVE );
203 ChangeState( EDITING );
207 void OnCursorKeyEvent( const Event& event )
209 int keyCode = event.p1.mInt;
211 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
215 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
219 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
223 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
229 void HandleCursorKey( int keyCode )
234 void OnTapEvent( const Event& event,
235 const Vector2& alignmentOffset )
237 unsigned int tapCount = event.p1.mUint;
241 ChangeState( EDITING );
243 float xPosition = event.p2.mFloat - alignmentOffset.x;
244 float yPosition = event.p3.mFloat - alignmentOffset.y;
246 GetClosestCursorPosition( mPrimaryCursorPosition, xPosition, yPosition, height );
247 mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
248 mUpdateCursorPosition = false;
250 mDecoratorUpdated = true;
252 else if( mSelectionEnabled &&
255 ChangeState( SELECTING );
259 void OnPanEvent( const Event& event,
260 const Vector2& controlSize,
261 const Vector2& alignmentOffset )
263 int state = event.p1.mInt;
265 if( Gesture::Started == state ||
266 Gesture::Continuing == state )
268 const Vector2& actualSize = mVisualModel->GetActualSize();
270 if( mHorizontalScrollingEnabled )
272 const float displacementX = event.p2.mFloat;
273 mScrollPosition.x += displacementX;
275 // Clamp between -space & 0 (and the text alignment).
276 const float contentWidth = actualSize.width;
277 if( contentWidth > controlSize.width )
279 const float space = ( contentWidth - controlSize.width ) + alignmentOffset.x;
280 mScrollPosition.x = ( mScrollPosition.x < -space ) ? -space : mScrollPosition.x;
281 mScrollPosition.x = ( mScrollPosition.x > -alignmentOffset.x ) ? -alignmentOffset.x : mScrollPosition.x;
283 mDecoratorUpdated = true;
287 mScrollPosition.x = 0.f;
291 if( mVerticalScrollingEnabled )
293 const float displacementY = event.p3.mFloat;
294 mScrollPosition.y += displacementY;
296 // Clamp between -space & 0 (and the text alignment).
297 if( actualSize.height > controlSize.height )
299 const float space = ( actualSize.height - controlSize.height ) + alignmentOffset.y;
300 mScrollPosition.y = ( mScrollPosition.y < -space ) ? -space : mScrollPosition.y;
301 mScrollPosition.y = ( mScrollPosition.y > -alignmentOffset.y ) ? -alignmentOffset.y : mScrollPosition.y;
303 mDecoratorUpdated = true;
307 mScrollPosition.y = 0.f;
313 void OnGrabHandleEvent( const Event& event )
315 unsigned int state = event.p1.mUint;
317 if( GRAB_HANDLE_PRESSED == state )
319 float xPosition = event.p2.mFloat;
320 float yPosition = event.p3.mFloat;
323 GetClosestCursorPosition( mPrimaryCursorPosition, xPosition, yPosition, height );
325 mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
326 mDecorator->HidePopup();
327 mDecoratorUpdated = true;
329 else if ( mGrabHandlePopupEnabled &&
330 GRAB_HANDLE_RELEASED == state )
332 mDecorator->ShowPopup();
336 void ChangeState( State newState )
338 if( mState != newState )
342 if( INACTIVE == mState )
344 mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
345 mDecorator->StopCursorBlink();
346 mDecorator->SetGrabHandleActive( false );
347 mDecorator->SetSelectionActive( false );
348 mDecorator->HidePopup();
349 mDecoratorUpdated = true;
351 else if ( SELECTING == mState )
353 mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
354 mDecorator->StopCursorBlink();
355 mDecorator->SetGrabHandleActive( false );
356 mDecorator->SetSelectionActive( true );
357 mDecoratorUpdated = true;
359 else if( EDITING == mState )
361 mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
362 if( mCursorBlinkEnabled )
364 mDecorator->StartCursorBlink();
366 if( mGrabHandleEnabled )
368 mDecorator->SetGrabHandleActive( true );
370 mDecorator->SetSelectionActive( false );
371 mDecoratorUpdated = true;
376 LineIndex GetClosestLine( float y )
378 LineIndex lineIndex( 0u );
380 const Vector<LineRun>& lines = mVisualModel->mLines;
381 for( float totalHeight = 0; lineIndex < lines.Count(); ++lineIndex )
383 totalHeight += lines[lineIndex].lineSize.height;
384 if( y < totalHeight )
393 void GetClosestCursorPosition( CharacterIndex& logical, float& visualX, float& visualY, float& height )
395 Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
396 Length numberOfLines = mVisualModel->mLines.Count();
397 if( 0 == numberOfGlyphs ||
403 // Transform to visual model coords
404 visualX -= mScrollPosition.x;
405 visualY -= mScrollPosition.y;
407 // Find which line is closest
408 LineIndex lineIndex( GetClosestLine( visualY ) );
410 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
411 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
413 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
414 const Vector2* const positionsBuffer = positions.Begin();
416 unsigned int closestGlyph = 0;
417 bool leftOfGlyph( false ); // which side of the glyph?
418 float closestDistance = MAX_FLOAT;
420 const LineRun& line = mVisualModel->mLines[lineIndex];
421 GlyphIndex startGlyph = line.glyphIndex;
422 GlyphIndex endGlyph = line.glyphIndex + line.numberOfGlyphs;
423 DALI_ASSERT_DEBUG( endGlyph <= glyphs.Count() && "Invalid line info" );
425 for( GlyphIndex i = startGlyph; i < endGlyph; ++i )
427 const GlyphInfo& glyphInfo = *( glyphsBuffer + i );
428 const Vector2& position = *( positionsBuffer + i );
429 float glyphX = position.x + glyphInfo.width*0.5f;
430 float glyphY = position.y + glyphInfo.height*0.5f;
432 float distanceToGlyph = fabsf( glyphX - visualX ) + fabsf( glyphY - visualY );
434 if( distanceToGlyph < closestDistance )
436 closestDistance = distanceToGlyph;
438 leftOfGlyph = ( visualX < glyphX );
442 // Calculate the logical position
443 logical = mVisualModel->GetCharacterIndex( closestGlyph );
445 // Returns the visual position of the glyph
446 visualX = positions[closestGlyph].x;
449 visualX += glyphs[closestGlyph].width;
454 else// if ( RTL ) TODO
460 height = line.lineSize.height;
463 void UpdateCursorPosition()
465 if( 0 == mVisualModel->mGlyphs.Count() )
470 // FIXME GetGlyphIndex() is behaving strangely
472 GlyphIndex cursorGlyph = mVisualModel->GetGlyphIndex( mPrimaryCursorPosition );
474 GlyphIndex cursorGlyph( 0u );
475 for( cursorGlyph = 0; cursorGlyph < mVisualModel->mGlyphs.Count(); ++cursorGlyph )
477 if( mPrimaryCursorPosition == mVisualModel->GetCharacterIndex( cursorGlyph ) )
484 float visualX( 0.0f );
485 float visualY( 0.0f );
486 LineIndex lineIndex( 0u );
487 const Vector<LineRun>& lineRuns = mVisualModel->mLines;
489 if( cursorGlyph > 0 )
493 visualX = mVisualModel->mGlyphPositions[ cursorGlyph ].x;
495 visualX += mVisualModel->mGlyphs[ cursorGlyph ].width;
497 // Find the line height
498 for( GlyphIndex lastGlyph = 0; lineIndex < lineRuns.Count(); ++lineIndex )
500 lastGlyph = (lineRuns[lineIndex].glyphIndex + lineRuns[lineIndex].numberOfGlyphs);
501 if( cursorGlyph < lastGlyph )
508 mDecorator->SetPosition( PRIMARY_CURSOR, visualX, visualY, lineRuns[lineIndex].lineSize.height );
509 mDecoratorUpdated = true;
512 LogicalModelPtr mLogicalModel;
513 VisualModelPtr mVisualModel;
514 DecoratorPtr mDecorator;
516 std::string mPlaceholderText;
519 * This is used to delay handling events until after the model has been updated.
520 * The number of updates to the model is minimized to improve performance.
522 vector<Event> mEventQueue; ///< The queue of touch events etc.
524 State mState; ///< Selection mode, edit mode etc.
526 CharacterIndex mPrimaryCursorPosition; ///< Index into logical model for primary cursor
527 CharacterIndex mSecondaryCursorPosition; ///< Index into logical model for secondary cursor
530 * 0,0 means that the top-left corner of the layout matches the top-left corner of the UI control.
531 * Typically this will have a negative value with scrolling occurs.
533 Vector2 mScrollPosition; ///< The text is offset by this position when scrolling.
535 bool mDecoratorUpdated : 1; ///< True if the decorator was updated during event processing
536 bool mCursorBlinkEnabled : 1; ///< True if cursor should blink when active
537 bool mGrabHandleEnabled : 1; ///< True if grab handle is enabled
538 bool mGrabHandlePopupEnabled : 1; ///< True if the grab handle popu-up should be shown
539 bool mSelectionEnabled : 1; ///< True if selection handles, highlight etc. are enabled
540 bool mHorizontalScrollingEnabled : 1; ///< True if horizontal scrolling is enabled
541 bool mVerticalScrollingEnabled : 1; ///< True if vertical scrolling is enabled
542 bool mUpdateCursorPosition : 1; ///< True if the visual position of the cursor must be recalculated
545 struct Controller::FontDefaults
548 : mDefaultPointSize(0.0f),
553 FontId GetFontId( TextAbstraction::FontClient& fontClient )
557 Dali::TextAbstraction::PointSize26Dot6 pointSize = mDefaultPointSize*64;
558 mFontId = fontClient.GetFontId( mDefaultFontFamily, mDefaultFontStyle, pointSize );
564 std::string mDefaultFontFamily;
565 std::string mDefaultFontStyle;
566 float mDefaultPointSize;
570 struct Controller::Impl
572 Impl( ControlInterface& controlInterface )
573 : mControlInterface( controlInterface ),
576 mFontDefaults( NULL ),
584 mOperationsPending( NO_OPERATION ),
585 mRecalculateNaturalSize( true )
587 mLogicalModel = LogicalModel::New();
588 mVisualModel = VisualModel::New();
590 mFontClient = TextAbstraction::FontClient::Get();
592 mView.SetVisualModel( mVisualModel );
600 ControlInterface& mControlInterface; ///< Reference to the text controller.
601 LogicalModelPtr mLogicalModel; ///< Pointer to the logical model.
602 VisualModelPtr mVisualModel; ///< Pointer to the visual model.
603 FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font.
604 Controller::TextInput* mTextInput; ///< Avoid allocating everything for text input until EnableTextInput().
605 TextAbstraction::FontClient mFontClient; ///< Handle to the font client.
606 View mView; ///< The view interface to the rendering back-end.
607 LayoutEngine mLayoutEngine; ///< The layout engine.
608 std::vector<ModifyEvent> mModifyEvents; ///< Temporary stores the text set until the next relayout.
609 Size mControlSize; ///< The size of the control.
610 Vector2 mAlignmentOffset; ///< Vertical and horizontal offset of the whole text inside the control due to alignment.
611 OperationsMask mOperationsPending; ///< Operations pending to be done to layout the text.
612 bool mRecalculateNaturalSize:1; ///< Whether the natural size needs to be recalculated.
615 ControllerPtr Controller::New( ControlInterface& controlInterface )
617 return ControllerPtr( new Controller( controlInterface ) );
620 void Controller::SetText( const std::string& text )
622 // Cancel previously queued inserts etc.
623 mImpl->mModifyEvents.clear();
625 // Keep until size negotiation
627 event.type = REPLACE_TEXT;
629 mImpl->mModifyEvents.push_back( event );
631 if( mImpl->mTextInput )
633 // Cancel previously queued events
634 mImpl->mTextInput->mEventQueue.clear();
636 // TODO - Hide selection decorations
640 void Controller::GetText( std::string& text ) const
642 if( !mImpl->mModifyEvents.empty() &&
643 REPLACE_TEXT == mImpl->mModifyEvents[0].type )
645 text = mImpl->mModifyEvents[0].text;
649 // TODO - Convert from UTF-32
653 void Controller::SetPlaceholderText( const std::string& text )
655 if( !mImpl->mTextInput )
657 mImpl->mTextInput->mPlaceholderText = text;
661 void Controller::GetPlaceholderText( std::string& text ) const
663 if( !mImpl->mTextInput )
665 text = mImpl->mTextInput->mPlaceholderText;
669 void Controller::SetDefaultFontFamily( const std::string& defaultFontFamily )
671 if( !mImpl->mFontDefaults )
673 mImpl->mFontDefaults = new Controller::FontDefaults();
676 mImpl->mFontDefaults->mDefaultFontFamily = defaultFontFamily;
677 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
678 mImpl->mOperationsPending = ALL_OPERATIONS;
679 mImpl->mRecalculateNaturalSize = true;
682 const std::string& Controller::GetDefaultFontFamily() const
684 if( mImpl->mFontDefaults )
686 return mImpl->mFontDefaults->mDefaultFontFamily;
692 void Controller::SetDefaultFontStyle( const std::string& defaultFontStyle )
694 if( !mImpl->mFontDefaults )
696 mImpl->mFontDefaults = new Controller::FontDefaults();
699 mImpl->mFontDefaults->mDefaultFontStyle = defaultFontStyle;
700 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
701 mImpl->mOperationsPending = ALL_OPERATIONS;
702 mImpl->mRecalculateNaturalSize = true;
705 const std::string& Controller::GetDefaultFontStyle() const
707 if( mImpl->mFontDefaults )
709 return mImpl->mFontDefaults->mDefaultFontStyle;
715 void Controller::SetDefaultPointSize( float pointSize )
717 if( !mImpl->mFontDefaults )
719 mImpl->mFontDefaults = new Controller::FontDefaults();
722 mImpl->mFontDefaults->mDefaultPointSize = pointSize;
723 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
724 mImpl->mOperationsPending = ALL_OPERATIONS;
725 mImpl->mRecalculateNaturalSize = true;
728 float Controller::GetDefaultPointSize() const
730 if( mImpl->mFontDefaults )
732 return mImpl->mFontDefaults->mDefaultPointSize;
738 void Controller::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
740 if( mImpl->mFontDefaults )
743 fontRun.characterRun.characterIndex = 0;
744 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
745 fontRun.fontId = mImpl->mFontDefaults->GetFontId( mImpl->mFontClient );
746 fontRun.isDefault = true;
748 fonts.PushBack( fontRun );
752 void Controller::EnableTextInput( DecoratorPtr decorator )
754 if( !mImpl->mTextInput )
756 mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, mImpl->mVisualModel, decorator );
760 void Controller::SetEnableCursorBlink( bool enable )
762 DALI_ASSERT_DEBUG( NULL != mImpl->mTextInput && "TextInput disabled" );
764 if( mImpl->mTextInput )
766 mImpl->mTextInput->mCursorBlinkEnabled = enable;
769 mImpl->mTextInput->mDecorator )
771 mImpl->mTextInput->mDecorator->StopCursorBlink();
776 bool Controller::GetEnableCursorBlink() const
778 if( mImpl->mTextInput )
780 return mImpl->mTextInput->mCursorBlinkEnabled;
786 const Vector2& Controller::GetScrollPosition() const
788 if( mImpl->mTextInput )
790 return mImpl->mTextInput->mScrollPosition;
793 return Vector2::ZERO;
796 const Vector2& Controller::GetAlignmentOffset() const
798 return mImpl->mAlignmentOffset;
801 Vector3 Controller::GetNaturalSize()
805 // Make sure the model is up-to-date before layouting
806 ProcessModifyEvents();
808 if( mImpl->mRecalculateNaturalSize )
810 // Operations that can be done only once until the text changes.
811 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
819 // Make sure the model is up-to-date before layouting
820 UpdateModel( onlyOnceOperations );
822 // Operations that need to be done if the size changes.
823 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
827 DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ),
828 static_cast<OperationsMask>( onlyOnceOperations |
830 naturalSize.GetVectorXY() );
832 // Do not do again the only once operations.
833 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
835 // Do the size related operations again.
836 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
838 // Stores the natural size to avoid recalculate it again
839 // unless the text/style changes.
840 mImpl->mVisualModel->SetNaturalSize( naturalSize.GetVectorXY() );
842 mImpl->mRecalculateNaturalSize = false;
846 naturalSize = mImpl->mVisualModel->GetNaturalSize();
852 float Controller::GetHeightForWidth( float width )
854 // Make sure the model is up-to-date before layouting
855 ProcessModifyEvents();
858 if( width != mImpl->mControlSize.width )
860 // Operations that can be done only once until the text changes.
861 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
869 // Make sure the model is up-to-date before layouting
870 UpdateModel( onlyOnceOperations );
872 // Operations that need to be done if the size changes.
873 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
877 DoRelayout( Size( width, MAX_FLOAT ),
878 static_cast<OperationsMask>( onlyOnceOperations |
882 // Do not do again the only once operations.
883 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
885 // Do the size related operations again.
886 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
890 layoutSize = mImpl->mVisualModel->GetActualSize();
893 return layoutSize.height;
896 bool Controller::Relayout( const Size& size )
898 if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
900 bool glyphsRemoved( false );
901 if( 0u != mImpl->mVisualModel->GetNumberOfGlyphPositions() )
903 mImpl->mVisualModel->SetGlyphPositions( NULL, 0u );
904 glyphsRemoved = true;
907 // Not worth to relayout if width or height is equal to zero.
908 return glyphsRemoved;
911 if( size != mImpl->mControlSize )
913 // Operations that need to be done if the size changes.
914 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
920 mImpl->mControlSize = size;
923 // Make sure the model is up-to-date before layouting
924 ProcessModifyEvents();
925 UpdateModel( mImpl->mOperationsPending );
928 bool updated = DoRelayout( mImpl->mControlSize,
929 mImpl->mOperationsPending,
932 // Do not re-do any operation until something changes.
933 mImpl->mOperationsPending = NO_OPERATION;
935 // After doing the text layout, the alignment offset to place the actor in the desired position can be calculated.
936 CalculateTextAlignment( size );
938 if( mImpl->mTextInput )
940 // Move the cursor, grab handle etc.
941 updated = mImpl->mTextInput->ProcessInputEvents( mImpl->mControlSize, mImpl->mAlignmentOffset ) || updated;
947 void Controller::ProcessModifyEvents()
949 std::vector<ModifyEvent>& events = mImpl->mModifyEvents;
951 for( unsigned int i=0; i<events.size(); ++i )
953 if( REPLACE_TEXT == events[0].type )
955 // A (single) replace event should come first, otherwise we wasted time processing NOOP events
956 DALI_ASSERT_DEBUG( 0 == i && "Unexpected REPLACE event" );
958 ReplaceTextEvent( events[0].text );
960 else if( INSERT_TEXT == events[0].type )
962 InsertTextEvent( events[0].text );
964 else if( DELETE_TEXT == events[0].type )
970 // Discard temporary text
974 void Controller::ReplaceTextEvent( const std::string& text )
977 mImpl->mLogicalModel->mText.Clear();
978 mImpl->mLogicalModel->mScriptRuns.Clear();
979 mImpl->mLogicalModel->mFontRuns.Clear();
980 mImpl->mLogicalModel->mLineBreakInfo.Clear();
981 mImpl->mLogicalModel->mWordBreakInfo.Clear();
982 mImpl->mLogicalModel->mBidirectionalParagraphInfo.Clear();
983 mImpl->mLogicalModel->mCharacterDirections.Clear();
984 mImpl->mLogicalModel->mBidirectionalLineInfo.Clear();
985 mImpl->mLogicalModel->mLogicalToVisualMap.Clear();
986 mImpl->mLogicalModel->mVisualToLogicalMap.Clear();
987 mImpl->mVisualModel->mGlyphs.Clear();
988 mImpl->mVisualModel->mGlyphsToCharacters.Clear();
989 mImpl->mVisualModel->mCharactersToGlyph.Clear();
990 mImpl->mVisualModel->mCharactersPerGlyph.Clear();
991 mImpl->mVisualModel->mGlyphsPerCharacter.Clear();
992 mImpl->mVisualModel->mGlyphPositions.Clear();
993 mImpl->mVisualModel->mLines.Clear();
995 // Convert text into UTF-32
996 Vector<Character>& utf32Characters = mImpl->mLogicalModel->mText;
997 utf32Characters.Resize( text.size() );
999 // This is a bit horrible but std::string returns a (signed) char*
1000 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
1002 // Transform a text array encoded in utf8 into an array encoded in utf32.
1003 // It returns the actual number of characters.
1004 Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
1005 utf32Characters.Resize( characterCount );
1007 // Reset the cursor position
1008 if( mImpl->mTextInput )
1010 mImpl->mTextInput->mPrimaryCursorPosition = characterCount;
1011 // TODO - handle secondary cursor
1014 // The natural size needs to be re-calculated.
1015 mImpl->mRecalculateNaturalSize = true;
1017 // Apply modifications to the model
1018 mImpl->mOperationsPending = ALL_OPERATIONS;
1019 UpdateModel( ALL_OPERATIONS );
1020 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
1022 UPDATE_ACTUAL_SIZE |
1026 void Controller::InsertTextEvent( const std::string& text )
1028 DALI_ASSERT_DEBUG( NULL != mImpl->mTextInput && "Unexpected InsertTextEvent" );
1030 // TODO - Optimize this
1031 mImpl->mLogicalModel->mScriptRuns.Clear();
1032 mImpl->mLogicalModel->mFontRuns.Clear();
1033 mImpl->mLogicalModel->mLineBreakInfo.Clear();
1034 mImpl->mLogicalModel->mWordBreakInfo.Clear();
1035 mImpl->mLogicalModel->mBidirectionalParagraphInfo.Clear();
1036 mImpl->mLogicalModel->mCharacterDirections.Clear();
1037 mImpl->mLogicalModel->mBidirectionalLineInfo.Clear();
1038 mImpl->mLogicalModel->mLogicalToVisualMap.Clear();
1039 mImpl->mLogicalModel->mVisualToLogicalMap.Clear();
1040 mImpl->mVisualModel->mGlyphs.Clear();
1041 mImpl->mVisualModel->mGlyphsToCharacters.Clear();
1042 mImpl->mVisualModel->mCharactersToGlyph.Clear();
1043 mImpl->mVisualModel->mCharactersPerGlyph.Clear();
1044 mImpl->mVisualModel->mGlyphsPerCharacter.Clear();
1045 mImpl->mVisualModel->mGlyphPositions.Clear();
1046 mImpl->mVisualModel->mLines.Clear();
1048 // Convert text into UTF-32
1049 Vector<Character> utf32Characters;
1050 utf32Characters.Resize( text.size() );
1052 // This is a bit horrible but std::string returns a (signed) char*
1053 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
1055 // Transform a text array encoded in utf8 into an array encoded in utf32.
1056 // It returns the actual number of characters.
1057 Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
1058 utf32Characters.Resize( characterCount );
1060 // Insert at current cursor position
1061 Vector<Character>& modifyText = mImpl->mLogicalModel->mText;
1062 CharacterIndex& cursorIndex = mImpl->mTextInput->mPrimaryCursorPosition;
1064 if( cursorIndex < modifyText.Count() )
1066 modifyText.Insert( modifyText.Begin() + cursorIndex, utf32Characters.Begin(), utf32Characters.End() );
1070 modifyText.Insert( modifyText.End(), utf32Characters.Begin(), utf32Characters.End() );
1073 // Advance the cursor position
1076 // The natural size needs to be re-calculated.
1077 mImpl->mRecalculateNaturalSize = true;
1079 // Apply modifications to the model; TODO - Optimize this
1080 mImpl->mOperationsPending = ALL_OPERATIONS;
1081 UpdateModel( ALL_OPERATIONS );
1082 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
1084 UPDATE_ACTUAL_SIZE |
1087 // Queue a cursor reposition event; this must wait until after DoRelayout()
1088 mImpl->mTextInput->mUpdateCursorPosition = true;
1091 void Controller::DeleteTextEvent()
1093 DALI_ASSERT_DEBUG( NULL != mImpl->mTextInput && "Unexpected InsertTextEvent" );
1095 // TODO - Optimize this
1096 mImpl->mLogicalModel->mScriptRuns.Clear();
1097 mImpl->mLogicalModel->mFontRuns.Clear();
1098 mImpl->mLogicalModel->mLineBreakInfo.Clear();
1099 mImpl->mLogicalModel->mWordBreakInfo.Clear();
1100 mImpl->mLogicalModel->mBidirectionalParagraphInfo.Clear();
1101 mImpl->mLogicalModel->mCharacterDirections.Clear();
1102 mImpl->mLogicalModel->mBidirectionalLineInfo.Clear();
1103 mImpl->mLogicalModel->mLogicalToVisualMap.Clear();
1104 mImpl->mLogicalModel->mVisualToLogicalMap.Clear();
1105 mImpl->mVisualModel->mGlyphs.Clear();
1106 mImpl->mVisualModel->mGlyphsToCharacters.Clear();
1107 mImpl->mVisualModel->mCharactersToGlyph.Clear();
1108 mImpl->mVisualModel->mCharactersPerGlyph.Clear();
1109 mImpl->mVisualModel->mGlyphsPerCharacter.Clear();
1110 mImpl->mVisualModel->mGlyphPositions.Clear();
1111 mImpl->mVisualModel->mLines.Clear();
1113 // Delte at current cursor position
1114 Vector<Character>& modifyText = mImpl->mLogicalModel->mText;
1115 CharacterIndex& cursorIndex = mImpl->mTextInput->mPrimaryCursorPosition;
1117 if( cursorIndex > 0 &&
1118 cursorIndex-1 < modifyText.Count() )
1120 modifyText.Remove( modifyText.Begin() + cursorIndex - 1 );
1122 // Cursor position retreat
1126 // The natural size needs to be re-calculated.
1127 mImpl->mRecalculateNaturalSize = true;
1129 // Apply modifications to the model; TODO - Optimize this
1130 mImpl->mOperationsPending = ALL_OPERATIONS;
1131 UpdateModel( ALL_OPERATIONS );
1132 mImpl->mOperationsPending = static_cast<OperationsMask>( LAYOUT |
1134 UPDATE_ACTUAL_SIZE |
1137 // Queue a cursor reposition event; this must wait until after DoRelayout()
1138 mImpl->mTextInput->mUpdateCursorPosition = true;
1141 void Controller::UpdateModel( OperationsMask operationsRequired )
1143 // Calculate the operations to be done.
1144 const OperationsMask operations = static_cast<OperationsMask>( mImpl->mOperationsPending & operationsRequired );
1146 Vector<Character>& utf32Characters = mImpl->mLogicalModel->mText;
1148 const Length numberOfCharacters = mImpl->mLogicalModel->GetNumberOfCharacters();
1150 Vector<LineBreakInfo>& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo;
1151 if( GET_LINE_BREAKS & operations )
1153 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
1154 // calculate the bidirectional info for each 'paragraph'.
1155 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
1156 // is not shaped together).
1157 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
1159 SetLineBreakInfo( utf32Characters,
1163 Vector<WordBreakInfo>& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo;
1164 if( GET_WORD_BREAKS & operations )
1166 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
1167 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
1169 SetWordBreakInfo( utf32Characters,
1173 const bool getScripts = GET_SCRIPTS & operations;
1174 const bool validateFonts = VALIDATE_FONTS & operations;
1176 Vector<ScriptRun>& scripts = mImpl->mLogicalModel->mScriptRuns;
1177 Vector<FontRun>& validFonts = mImpl->mLogicalModel->mFontRuns;
1179 if( getScripts || validateFonts )
1181 // Validates the fonts assigned by the application or assigns default ones.
1182 // It makes sure all the characters are going to be rendered by the correct font.
1183 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
1187 // Retrieves the scripts used in the text.
1188 multilanguageSupport.SetScripts( utf32Characters,
1195 if( 0u == validFonts.Count() )
1197 // Copy the requested font defaults received via the property system.
1198 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
1199 GetDefaultFonts( validFonts, numberOfCharacters );
1202 // Validates the fonts. If there is a character with no assigned font it sets a default one.
1203 // After this call, fonts are validated.
1204 multilanguageSupport.ValidateFonts( utf32Characters,
1210 Vector<Character> mirroredUtf32Characters;
1211 bool textMirrored = false;
1212 if( BIDI_INFO & operations )
1214 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
1215 // bidirectional info.
1217 Length numberOfParagraphs = 0u;
1219 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
1220 for( Length index = 0u; index < numberOfCharacters; ++index )
1222 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
1224 ++numberOfParagraphs;
1228 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mImpl->mLogicalModel->mBidirectionalParagraphInfo;
1229 bidirectionalInfo.Reserve( numberOfParagraphs );
1231 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
1232 SetBidirectionalInfo( utf32Characters,
1235 bidirectionalInfo );
1237 if( 0u != bidirectionalInfo.Count() )
1239 // This paragraph has right to left text. Some characters may need to be mirrored.
1240 // TODO: consider if the mirrored string can be stored as well.
1242 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
1244 // Only set the character directions if there is right to left characters.
1245 Vector<CharacterDirection>& directions = mImpl->mLogicalModel->mCharacterDirections;
1246 directions.Resize( numberOfCharacters );
1248 GetCharactersDirection( bidirectionalInfo,
1253 // There is no right to left characters. Clear the directions vector.
1254 mImpl->mLogicalModel->mCharacterDirections.Clear();
1259 Vector<GlyphInfo>& glyphs = mImpl->mVisualModel->mGlyphs;
1260 Vector<CharacterIndex>& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters;
1261 Vector<Length>& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph;
1262 if( SHAPE_TEXT & operations )
1264 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
1266 ShapeText( textToShape,
1271 glyphsToCharactersMap,
1272 charactersPerGlyph );
1275 const Length numberOfGlyphs = glyphs.Count();
1277 if( GET_GLYPH_METRICS & operations )
1279 mImpl->mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
1282 if( 0u != numberOfGlyphs )
1284 // Create the glyph to character conversion table and the 'number of glyphs' per character.
1285 mImpl->mVisualModel->CreateCharacterToGlyphTable(numberOfCharacters );
1286 mImpl->mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
1290 bool Controller::DoRelayout( const Size& size,
1291 OperationsMask operationsRequired,
1294 bool viewUpdated( false );
1296 // Calculate the operations to be done.
1297 const OperationsMask operations = static_cast<OperationsMask>( mImpl->mOperationsPending & operationsRequired );
1299 if( LAYOUT & operations )
1301 // Some vectors with data needed to layout and reorder may be void
1302 // after the first time the text has been laid out.
1303 // Fill the vectors again.
1305 Length numberOfGlyphs = mImpl->mVisualModel->GetNumberOfGlyphs();
1307 Vector<LineBreakInfo>& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo;
1308 Vector<WordBreakInfo>& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo;
1309 Vector<GlyphInfo>& glyphs = mImpl->mVisualModel->mGlyphs;
1310 Vector<CharacterIndex>& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters;
1311 Vector<Length>& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph;
1313 // Set the layout parameters.
1314 LayoutParameters layoutParameters( size,
1315 mImpl->mLogicalModel->mText.Begin(),
1316 lineBreakInfo.Begin(),
1317 wordBreakInfo.Begin(),
1320 glyphsToCharactersMap.Begin(),
1321 charactersPerGlyph.Begin() );
1323 // The laid-out lines.
1324 // It's not possible to know in how many lines the text is going to be laid-out,
1325 // but it can be resized at least with the number of 'paragraphs' to avoid
1326 // some re-allocations.
1327 Vector<LineRun>& lines = mImpl->mVisualModel->mLines;
1329 // Delete any previous laid out lines before setting the new ones.
1332 // The capacity of the bidirectional paragraph info is the number of paragraphs.
1333 lines.Reserve( mImpl->mLogicalModel->mBidirectionalParagraphInfo.Capacity() );
1335 // Resize the vector of positions to have the same size than the vector of glyphs.
1336 Vector<Vector2>& glyphPositions = mImpl->mVisualModel->mGlyphPositions;
1337 glyphPositions.Resize( numberOfGlyphs );
1339 // Update the visual model.
1340 viewUpdated = mImpl->mLayoutEngine.LayoutText( layoutParameters,
1347 // Reorder the lines
1348 if( REORDER & operations )
1350 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mImpl->mLogicalModel->mBidirectionalParagraphInfo;
1352 // Check first if there are paragraphs with bidirectional info.
1353 if( 0u != bidirectionalInfo.Count() )
1356 const Length numberOfLines = mImpl->mVisualModel->GetNumberOfLines();
1358 // Reorder the lines.
1359 Vector<BidirectionalLineInfoRun> lineBidirectionalInfoRuns;
1360 lineBidirectionalInfoRuns.Reserve( numberOfLines ); // Reserve because is not known yet how many lines have right to left characters.
1361 ReorderLines( bidirectionalInfo,
1363 lineBidirectionalInfoRuns );
1365 // Set the bidirectional info into the model.
1366 const Length numberOfBidirectionalInfoRuns = lineBidirectionalInfoRuns.Count();
1367 mImpl->mLogicalModel->SetVisualToLogicalMap( lineBidirectionalInfoRuns.Begin(),
1368 numberOfBidirectionalInfoRuns );
1370 // Set the bidirectional info per line into the layout parameters.
1371 layoutParameters.lineBidirectionalInfoRunsBuffer = lineBidirectionalInfoRuns.Begin();
1372 layoutParameters.numberOfBidirectionalInfoRuns = numberOfBidirectionalInfoRuns;
1374 // Get the character to glyph conversion table and set into the layout.
1375 layoutParameters.charactersToGlyphsBuffer = mImpl->mVisualModel->mCharactersToGlyph.Begin();
1377 // Get the glyphs per character table and set into the layout.
1378 layoutParameters.glyphsPerCharacterBuffer = mImpl->mVisualModel->mGlyphsPerCharacter.Begin();
1380 // Re-layout the text. Reorder those lines with right to left characters.
1381 mImpl->mLayoutEngine.ReLayoutRightToLeftLines( layoutParameters,
1384 // Free the allocated memory used to store the conversion table in the bidirectional line info run.
1385 for( Vector<BidirectionalLineInfoRun>::Iterator it = lineBidirectionalInfoRuns.Begin(),
1386 endIt = lineBidirectionalInfoRuns.End();
1390 BidirectionalLineInfoRun& bidiLineInfo = *it;
1392 free( bidiLineInfo.visualToLogicalMap );
1397 if( ALIGN & operations )
1399 mImpl->mLayoutEngine.Align( layoutParameters,
1405 // Sets the actual size.
1406 if( UPDATE_ACTUAL_SIZE & operations )
1408 mImpl->mVisualModel->SetActualSize( layoutSize );
1414 layoutSize = mImpl->mVisualModel->GetActualSize();
1420 void Controller::CalculateTextAlignment( const Size& size )
1422 // TODO : Calculate the vertical offset.
1424 // Get the direction of the first character.
1425 const CharacterDirection firstParagraphDirection = mImpl->mLogicalModel->GetCharacterDirection( 0u );
1427 const Size& actualSize = mImpl->mVisualModel->GetActualSize();
1429 // If the first paragraph is right to left swap ALIGN_BEGIN and ALIGN_END;
1430 LayoutEngine::Alignment alignment = mImpl->mLayoutEngine.GetAlignment();
1431 if( firstParagraphDirection &&
1432 ( LayoutEngine::ALIGN_CENTER != alignment ) )
1434 if( LayoutEngine::ALIGN_BEGIN == alignment )
1436 alignment = LayoutEngine::ALIGN_END;
1440 alignment = LayoutEngine::ALIGN_BEGIN;
1446 case LayoutEngine::ALIGN_BEGIN:
1448 mImpl->mAlignmentOffset = Vector2::ZERO;
1451 case LayoutEngine::ALIGN_CENTER:
1453 mImpl->mAlignmentOffset.y = 0.f;
1454 const int intOffset = static_cast<int>( 0.5f * ( size.width - actualSize.width ) ); // try to avoid pixel alignment.
1455 mImpl->mAlignmentOffset.x = static_cast<float>( intOffset );
1458 case LayoutEngine::ALIGN_END:
1460 mImpl->mAlignmentOffset.y = 0.f;
1461 mImpl->mAlignmentOffset.x = size.width - actualSize.width;
1467 View& Controller::GetView()
1469 return mImpl->mView;
1472 LayoutEngine& Controller::GetLayoutEngine()
1474 return mImpl->mLayoutEngine;
1477 void Controller::RequestRelayout()
1479 mImpl->mControlInterface.RequestTextRelayout();
1482 void Controller::KeyboardFocusGainEvent()
1484 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusGainEvent" );
1486 if( mImpl->mTextInput )
1488 TextInput::Event event( TextInput::KEYBOARD_FOCUS_GAIN_EVENT );
1489 mImpl->mTextInput->mEventQueue.push_back( event );
1495 void Controller::KeyboardFocusLostEvent()
1497 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusLostEvent" );
1499 if( mImpl->mTextInput )
1501 TextInput::Event event( TextInput::KEYBOARD_FOCUS_LOST_EVENT );
1502 mImpl->mTextInput->mEventQueue.push_back( event );
1508 bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
1510 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyEvent" );
1512 if( mImpl->mTextInput &&
1513 keyEvent.state == KeyEvent::Down )
1515 int keyCode = keyEvent.keyCode;
1516 const std::string& keyString = keyEvent.keyPressed;
1518 // Pre-process to separate modifying events from non-modifying input events.
1519 if( Dali::DALI_KEY_ESCAPE == keyCode )
1521 // Escape key is a special case which causes focus loss
1522 KeyboardFocusLostEvent();
1524 else if( Dali::DALI_KEY_CURSOR_LEFT == keyCode ||
1525 Dali::DALI_KEY_CURSOR_RIGHT == keyCode ||
1526 Dali::DALI_KEY_CURSOR_UP == keyCode ||
1527 Dali::DALI_KEY_CURSOR_DOWN == keyCode )
1529 TextInput::Event event( TextInput::CURSOR_KEY_EVENT );
1530 event.p1.mInt = keyCode;
1531 mImpl->mTextInput->mEventQueue.push_back( event );
1533 else if( Dali::DALI_KEY_BACKSPACE == keyCode )
1535 // Queue a delete event
1537 event.type = DELETE_TEXT;
1538 mImpl->mModifyEvents.push_back( event );
1540 else if( !keyString.empty() )
1542 // Queue an insert event
1544 event.type = INSERT_TEXT;
1545 event.text = keyString;
1546 mImpl->mModifyEvents.push_back( event );
1555 void Controller::TapEvent( unsigned int tapCount, float x, float y )
1557 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected TapEvent" );
1559 if( mImpl->mTextInput )
1561 TextInput::Event event( TextInput::TAP_EVENT );
1562 event.p1.mUint = tapCount;
1563 event.p2.mFloat = x;
1564 event.p3.mFloat = y;
1565 mImpl->mTextInput->mEventQueue.push_back( event );
1571 void Controller::PanEvent( Gesture::State state, const Vector2& displacement )
1573 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected PanEvent" );
1575 if( mImpl->mTextInput )
1577 TextInput::Event event( TextInput::PAN_EVENT );
1578 event.p1.mInt = state;
1579 event.p2.mFloat = displacement.x;
1580 event.p3.mFloat = displacement.y;
1581 mImpl->mTextInput->mEventQueue.push_back( event );
1587 void Controller::GrabHandleEvent( GrabHandleState state, float x, float y )
1589 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected GrabHandleEvent" );
1591 if( mImpl->mTextInput )
1593 TextInput::Event event( TextInput::GRAB_HANDLE_EVENT );
1594 event.p1.mUint = state;
1595 event.p2.mFloat = x;
1596 event.p3.mFloat = y;
1597 mImpl->mTextInput->mEventQueue.push_back( event );
1603 Controller::~Controller()
1608 Controller::Controller( ControlInterface& controlInterface )
1611 mImpl = new Controller::Impl( controlInterface );
1616 } // namespace Toolkit