X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali-toolkit%2Fpublic-api%2Ftext%2Ftext-controller.cpp;h=58fd536176d3b972cec91cee5b0cf03b882c95cf;hb=a44ea1b60d12c879dacaa5f78b6c042b59ed6c2c;hp=6fe0f33832bfd9c0f4d5bc90204112cbf65dba87;hpb=2dd044328238768ae8b27a223cb7d0f5cda53513;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/public-api/text/text-controller.cpp b/dali-toolkit/public-api/text/text-controller.cpp index 6fe0f33..58fd536 100644 --- a/dali-toolkit/public-api/text/text-controller.cpp +++ b/dali-toolkit/public-api/text/text-controller.cpp @@ -18,18 +18,24 @@ // CLASS HEADER #include -// EXTERNAL INCLUDES -#include - // INTERNAL INCLUDES #include #include #include #include #include +#include +#include #include #include +// EXTERNAL INCLUDES +#include +#include +#include + +using std::vector; + namespace Dali { @@ -39,10 +45,213 @@ namespace Toolkit namespace Text { +struct Controller::TextInput +{ + // Used to queue input events until DoRelayout() + enum EventType + { + KEYBOARD_FOCUS_GAIN_EVENT, + KEYBOARD_FOCUS_LOST_EVENT, + TAP_EVENT, + GRAB_HANDLE_EVENT + }; + + union Param + { + int mInt; + unsigned int mUint; + float mFloat; + }; + + struct Event + { + Event( EventType eventType ) + : type( eventType ) + { + p1.mInt = 0; + p2.mInt = 0; + } + + EventType type; + Param p1; + Param p2; + Param p3; + }; + + enum State + { + INACTIVE, + SELECTING, + EDITING + }; + + TextInput( LogicalModelPtr logicalModel, + VisualModelPtr visualModel, + DecoratorPtr decorator ) + : mLogicalModel( logicalModel ), + mVisualModel( visualModel ), + mDecorator( decorator ), + mState( INACTIVE ) + { + } + + /** + * @brief Helper to move the cursor, grab handle etc. + */ + bool ProcessTouchEvents() + { + mDecoratorUpdated = false; + + if( mDecorator ) + { + for( vector::iterator iter = mEventQueue.begin(); iter != mEventQueue.end(); ++iter ) + { + switch( iter->type ) + { + case KEYBOARD_FOCUS_GAIN_EVENT: + { + OnKeyboardFocus( true ); + break; + } + case KEYBOARD_FOCUS_LOST_EVENT: + { + OnKeyboardFocus( false ); + break; + } + case TAP_EVENT: + { + OnTapEvent( *iter ); + break; + } + case GRAB_HANDLE_EVENT: + { + OnGrabHandleEvent( *iter ); + break; + } + } + } + } + + mEventQueue.clear(); + + return mDecoratorUpdated; + } + + void OnKeyboardFocus( bool hasFocus ) + { + // TODO + } + + void OnTapEvent( const Event& event ) + { + if( 1u == event.p1.mUint ) + { + mState = TextInput::EDITING; + mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + mDecorator->StartCursorBlink(); + mDecorator->SetGrabHandleActive( true ); + + float xPosition = event.p2.mFloat; + float yPosition = event.p3.mFloat; + float height(0.0f); + GetClosestCursorPosition( xPosition, yPosition, height ); + mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height ); + + mDecoratorUpdated = true; + } + else if( 2u == event.p1.mUint ) + { + mState = TextInput::SELECTING; + mDecorator->SetGrabHandleActive( false ); + mDecorator->SetSelectionActive( true ); + mDecoratorUpdated = true; + } + } + + void OnGrabHandleEvent( const Event& event ) + { + unsigned int state = event.p1.mUint; + + if( GRAB_HANDLE_PRESSED == state ) + { + float xPosition = event.p2.mFloat; + float yPosition = event.p3.mFloat; + float height(0.0f); + + GetClosestCursorPosition( xPosition, yPosition, height ); + + mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height ); + mDecoratorUpdated = true; + } + } + + void GetClosestCursorPosition( float& x, float& y, float& height ) + { + // TODO - Look at LineRuns first + + Text::Length numberOfGlyphs = mVisualModel->GetNumberOfGlyphs(); + if( 0 == numberOfGlyphs ) + { + return; + } + + Vector glyphs; + glyphs.Resize( numberOfGlyphs ); + mVisualModel->GetGlyphs( &glyphs[0], 0, numberOfGlyphs ); + + std::vector positions; + positions.resize( numberOfGlyphs ); + mVisualModel->GetGlyphPositions( &positions[0], 0, numberOfGlyphs ); + + unsigned int closestGlyph = 0; + float closestDistance = std::numeric_limits::max(); + + for( unsigned int i=0; i mEventQueue; ///< The queue of touch events etc. + + bool mDecoratorUpdated; +}; + struct Controller::Impl { - Impl() - : mNewTextArrived( false ) + Impl( ControlInterface& controlInterface ) + : mControlInterface( controlInterface ), + mNewText(), + mOperations( NO_OPERATION ), + mControlSize(), + mTextInput( NULL ) { mLogicalModel = LogicalModel::New(); mVisualModel = VisualModel::New(); @@ -52,8 +261,14 @@ struct Controller::Impl mFontClient = TextAbstraction::FontClient::Get(); } + ~Impl() + { + delete mTextInput; + } + + ControlInterface& mControlInterface; + std::string mNewText; - bool mNewTextArrived; LogicalModelPtr mLogicalModel; VisualModelPtr mVisualModel; @@ -63,60 +278,199 @@ struct Controller::Impl LayoutEngine mLayoutEngine; TextAbstraction::FontClient mFontClient; + + OperationsMask mOperations; + + Size mControlSize; + + // Avoid allocating everything for text input until EnableTextInput() + Controller::TextInput* mTextInput; }; -ControllerPtr Controller::New() +ControllerPtr Controller::New( ControlInterface& controlInterface ) { - return ControllerPtr( new Controller() ); + return ControllerPtr( new Controller( controlInterface ) ); } void Controller::SetText( const std::string& text ) { // Keep until size negotiation mImpl->mNewText = text; - mImpl->mNewTextArrived = true; + mImpl->mOperations = ALL_OPERATIONS; + + if( mImpl->mTextInput ) + { + // Cancel previously queued events + mImpl->mTextInput->mEventQueue.clear(); + + // TODO - Hide selection decorations + } +} + +void Controller::EnableTextInput( DecoratorPtr decorator ) +{ + if( !mImpl->mTextInput ) + { + mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, mImpl->mVisualModel, decorator ); + } } bool Controller::Relayout( const Vector2& size ) { + if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) ) + { + // Not worth to relayout if width or height is equal to zero. + return false; + } + + bool updated = false; + + if( size != mImpl->mControlSize ) + { + updated = DoRelayout( size, mImpl->mOperations ); + + // Do not re-do any operation until something changes. + mImpl->mOperations = NO_OPERATION; + + mImpl->mControlSize = size; + } + + if( mImpl->mTextInput ) + { + // Move the cursor, grab handle etc. + updated = mImpl->mTextInput->ProcessTouchEvents() || updated; + } + + return updated; +} + +bool Controller::DoRelayout( const Vector2& size, OperationsMask operations ) +{ bool viewUpdated( false ); - if( mImpl->mNewTextArrived ) + Vector utf32Characters; + Length characterCount = 0u; + if( CONVERT_TO_UTF32 & operations ) { std::string& text = mImpl->mNewText; // Convert text into UTF-32 - Vector utf32Characters; utf32Characters.Resize( text.size() ); // This is a bit horrible but std::string returns a (signed) char* const uint8_t* utf8 = reinterpret_cast( text.c_str() ); - Length characterCount = Utf8ToUtf32( utf8, text.size(), &utf32Characters[0] ); + // Transform a text array encoded in utf8 into an array encoded in utf32. + // It returns the actual number of characters. + characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() ); utf32Characters.Resize( characterCount ); - Vector scripts; + // Sets the text into the model. + mImpl->mLogicalModel->SetText( utf32Characters.Begin(), characterCount ); + + // Discard temporary text + text.clear(); + } + + Vector lineBreakInfo; + if( GET_LINE_BREAKS & operations ) + { + // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to + // calculate the bidirectional info for each 'paragraph'. + // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines + // is not shaped together). + lineBreakInfo.Resize( characterCount, TextAbstraction::LINE_NO_BREAK ); + + SetLineBreakInfo( utf32Characters, + lineBreakInfo ); + + mImpl->mLogicalModel->SetLineBreakInfo( lineBreakInfo.Begin(), characterCount ); + } + + const bool getScripts = GET_SCRIPTS & operations; + const bool validateFonts = VALIDATE_FONTS & operations; + + Vector scripts; + Vector fonts; + if( getScripts || validateFonts ) + { + // Validates the fonts assigned by the application or assigns default ones. + // It makes sure all the characters are going to be rendered by the correct font. MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get(); - multilanguageSupport.SetScripts( utf32Characters, - scripts ); + if( getScripts ) + { + // Retrieves the scripts used in the text. + multilanguageSupport.SetScripts( utf32Characters, + lineBreakInfo, + scripts ); + + // Sets the scripts into the model. + mImpl->mLogicalModel->SetScripts( scripts.Begin(), scripts.Count() ); + } + + if( validateFonts ) + { + // Validates the fonts. If there is a character with no assigned font it sets a default one. + // After this call, fonts are validated. + multilanguageSupport.ValidateFonts( utf32Characters, + scripts, + fonts ); + + // Sets the fonts into the model. + mImpl->mLogicalModel->SetFonts( fonts.Begin(), fonts.Count() ); + } + } - Vector fonts; - multilanguageSupport.ValidateFonts( utf32Characters, - scripts, - fonts ); + Vector glyphs; + Vector characterIndices; + Vector charactersPerGlyph; + if( SHAPE_TEXT & operations ) + { + // Shapes the text. + ShapeText( utf32Characters, + lineBreakInfo, + scripts, + fonts, + glyphs, + characterIndices, + charactersPerGlyph ); + } - // Manipulate the logical model - mImpl->mLogicalModel->SetText( &utf32Characters[0], characterCount ); - mImpl->mLogicalModel->SetScripts( &scripts[0], scripts.Count() ); - mImpl->mLogicalModel->SetFonts( &fonts[0], fonts.Count() ); + if( GET_GLYPH_METRICS & operations ) + { + mImpl->mFontClient.GetGlyphMetrics( glyphs.Begin(), glyphs.Count() ); + } - // Update the visual model - mImpl->mLayoutEngine.UpdateVisualModel( size, *mImpl->mLogicalModel, *mImpl->mVisualModel ); + if( LAYOUT & operations ) + { + if( 0u == glyphs.Count() ) + { + const Length numberOfGlyphs = mImpl->mVisualModel->GetNumberOfGlyphs(); - // Discard temporary text - mImpl->mNewTextArrived = false; - text.clear(); + glyphs.Resize( numberOfGlyphs ); + characterIndices.Resize( numberOfGlyphs ); + charactersPerGlyph.Resize( numberOfGlyphs ); + + mImpl->mVisualModel->GetGlyphs( glyphs.Begin(), + 0u, + numberOfGlyphs ); + + mImpl->mVisualModel->GetGlyphToCharacterMap( characterIndices.Begin(), + 0u, + numberOfGlyphs ); + + mImpl->mVisualModel->GetCharactersPerGlyphMap( charactersPerGlyph.Begin(), + 0u, + numberOfGlyphs ); + } + + // Update the visual model + mImpl->mLayoutEngine.UpdateVisualModel( size, + glyphs, + characterIndices, + charactersPerGlyph, + *mImpl->mVisualModel ); viewUpdated = true; } @@ -124,6 +478,63 @@ bool Controller::Relayout( const Vector2& size ) return viewUpdated; } +Vector3 Controller::GetNaturalSize() +{ + // Operations that can be done only once until the text changes. + const OperationsMask onlyOnceOperations = static_cast( CONVERT_TO_UTF32 | + GET_SCRIPTS | + VALIDATE_FONTS | + GET_LINE_BREAKS | + GET_WORD_BREAKS | + SHAPE_TEXT | + GET_GLYPH_METRICS ); + + // Operations that need to be done if the size or the text changes. + const OperationsMask sizeOperations = static_cast( LAYOUT | + REORDER ); + + const float maxFloat = std::numeric_limits::max(); + DoRelayout( Vector2( maxFloat, maxFloat ), + static_cast( onlyOnceOperations | + sizeOperations ) ); + + // Do not do again the only once operations. + mImpl->mOperations = static_cast( mImpl->mOperations & ~onlyOnceOperations ); + + // Do the size related operations again. + mImpl->mOperations = static_cast( mImpl->mOperations | sizeOperations ); + + return Vector3( mImpl->mVisualModel->GetNaturalSize() ); +} + +float Controller::GetHeightForWidth( float width ) +{ + // Operations that can be done only once until the text changes. + const OperationsMask onlyOnceOperations = static_cast( CONVERT_TO_UTF32 | + GET_SCRIPTS | + VALIDATE_FONTS | + GET_LINE_BREAKS | + GET_WORD_BREAKS | + SHAPE_TEXT | + GET_GLYPH_METRICS ); + + // Operations that need to be done if the size or the text changes. + const OperationsMask sizeOperations = static_cast( LAYOUT | + REORDER ); + + DoRelayout( Size( width, 0.f ), + static_cast( onlyOnceOperations | + sizeOperations ) ); + + // Do not do again the only once operations. + mImpl->mOperations = static_cast( mImpl->mOperations & ~onlyOnceOperations ); + + // Do the size related operations again. + mImpl->mOperations = static_cast( mImpl->mOperations | sizeOperations ); + + return mImpl->mVisualModel->GetActualSize().height; +} + View& Controller::GetView() { return mImpl->mView; @@ -134,15 +545,78 @@ LayoutEngine& Controller::GetLayoutEngine() return mImpl->mLayoutEngine; } +void Controller::RequestRelayout() +{ + mImpl->mControlInterface.RequestTextRelayout(); +} + +void Controller::KeyboardFocusGainEvent() +{ + DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusGainEvent" ); + + if( mImpl->mTextInput ) + { + TextInput::Event event( TextInput::KEYBOARD_FOCUS_GAIN_EVENT ); + mImpl->mTextInput->mEventQueue.push_back( event ); + + RequestRelayout(); + } +} + +void Controller::KeyboardFocusLostEvent() +{ + DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusLostEvent" ); + + if( mImpl->mTextInput ) + { + TextInput::Event event( TextInput::KEYBOARD_FOCUS_LOST_EVENT ); + mImpl->mTextInput->mEventQueue.push_back( event ); + + RequestRelayout(); + } +} + +void Controller::TapEvent( unsigned int tapCount, float x, float y ) +{ + DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected TapEvent" ); + + if( mImpl->mTextInput ) + { + TextInput::Event event( TextInput::TAP_EVENT ); + event.p1.mUint = tapCount; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mTextInput->mEventQueue.push_back( event ); + + RequestRelayout(); + } +} + +void Controller::GrabHandleEvent( GrabHandleState state, float x, float y ) +{ + DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected GrabHandleEvent" ); + + if( mImpl->mTextInput ) + { + TextInput::Event event( TextInput::GRAB_HANDLE_EVENT ); + event.p1.mUint = state; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mTextInput->mEventQueue.push_back( event ); + + RequestRelayout(); + } +} + Controller::~Controller() { delete mImpl; } -Controller::Controller() +Controller::Controller( ControlInterface& controlInterface ) : mImpl( NULL ) { - mImpl = new Controller::Impl(); + mImpl = new Controller::Impl( controlInterface ); } } // namespace Text