From 09f35f81061ca470e79ac674024b9e587ff44e7f Mon Sep 17 00:00:00 2001 From: Paul Wisbey Date: Sat, 25 Apr 2015 12:40:16 +0100 Subject: [PATCH] PlaceholderText + IMF improvements Change-Id: I14e33827fca8df01ac22699e59908f47c4341c2d --- .../src/dali-toolkit/utc-Dali-TextLabel.cpp | 2 +- .../controls/text-controls/text-field-impl.cpp | 103 +++- .../internal/text/decorator/text-decorator.cpp | 2 +- .../internal/text/text-controller-impl.cpp | 307 ++++++++++- dali-toolkit/internal/text/text-controller-impl.h | 126 ++++- dali-toolkit/internal/text/text-controller.cpp | 584 +++++++++++---------- dali-toolkit/internal/text/text-controller.h | 93 +++- dali-toolkit/internal/text/visual-model-impl.cpp | 8 +- .../public-api/controls/text-controls/text-field.h | 4 +- 9 files changed, 871 insertions(+), 358 deletions(-) diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp index 498c40e..c0e4bec 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp @@ -178,7 +178,7 @@ int UtcDaliToolkitTextLabelGetPropertyP(void) // Check label defaults are correct DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::RENDERING_BACKEND ), Text::RENDERING_SHARED_ATLAS, TEST_LOCATION ); - DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::TEXT_COLOR ), Color::WHITE, TEST_LOCATION ); + DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::TEXT_COLOR ), Color::BLACK, TEST_LOCATION ); DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::SHADOW_OFFSET ), Vector2::ZERO, TEST_LOCATION ); DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::SHADOW_COLOR ), Color::BLACK, TEST_LOCATION ); DALI_TEST_EQUALS( label.GetProperty( TextLabel::Property::UNDERLINE_ENABLED ), false, TEST_LOCATION ); diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp index d6e763d..0dc5fd7 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -20,6 +20,7 @@ // EXTERNAL INCLUDES #include +#include #include #include #include @@ -81,8 +82,9 @@ BaseHandle Create() DALI_TYPE_REGISTRATION_BEGIN( Toolkit::TextField, Toolkit::Control, Create ); DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "rendering-backend", INTEGER, RENDERING_BACKEND ) -DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "placeholder-text", STRING, PLACEHOLDER_TEXT ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "text", STRING, TEXT ) +DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "placeholder-text", STRING, PLACEHOLDER_TEXT ) +DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "placeholder-text-focused", STRING, PLACEHOLDER_TEXT_FOCUSED ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "font-family", STRING, FONT_FAMILY ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "font-style", STRING, FONT_STYLE ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "point-size", FLOAT, POINT_SIZE ) @@ -91,6 +93,7 @@ DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "exceed-policy", DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "horizontal-alignment", STRING, HORIZONTAL_ALIGNMENT ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "vertical-alignment", STRING, VERTICAL_ALIGNMENT ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "text-color", VECTOR4, TEXT_COLOR ) +DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "placeholder-text-color", VECTOR4, PLACEHOLDER_TEXT_COLOR ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "shadow-offset", VECTOR2, SHADOW_OFFSET ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "shadow-color", VECTOR4, SHADOW_COLOR ) DALI_PROPERTY_REGISTRATION( Toolkit, TextField, "primary-cursor-color", VECTOR4, PRIMARY_CURSOR_COLOR ) @@ -151,19 +154,27 @@ void TextField::SetProperty( BaseObject* object, Property::Index index, const Pr } break; } + case Toolkit::TextField::Property::TEXT: + { + if( impl.mController ) + { + impl.mController->SetText( value.Get< std::string >() ); + } + break; + } case Toolkit::TextField::Property::PLACEHOLDER_TEXT: { if( impl.mController ) { - //impl.mController->SetPlaceholderText( value.Get< std::string >() ); TODO + impl.mController->SetPlaceholderText( PLACEHOLDER_TYPE_INACTIVE, value.Get< std::string >() ); } break; } - case Toolkit::TextField::Property::TEXT: + case Toolkit::TextField::Property::PLACEHOLDER_TEXT_FOCUSED: { if( impl.mController ) { - impl.mController->SetText( value.Get< std::string >() ); + impl.mController->SetPlaceholderText( PLACEHOLDER_TYPE_ACTIVE, value.Get< std::string >() ); } break; } @@ -255,6 +266,19 @@ void TextField::SetProperty( BaseObject* object, Property::Index index, const Pr } break; } + case Toolkit::TextField::Property::PLACEHOLDER_TEXT_COLOR: + { + if ( impl.mController ) + { + Vector4 textColor = value.Get< Vector4 >(); + if ( impl.mController->GetPlaceholderTextColor() != textColor ) + { + impl.mController->SetPlaceholderTextColor( textColor ); + impl.RequestTextRelayout(); + } + } + break; + } case Toolkit::TextField::Property::SHADOW_OFFSET: { if( impl.mController ) @@ -448,22 +472,32 @@ Property::Value TextField::GetProperty( BaseObject* object, Property::Index inde value = impl.mRenderingBackend; break; } + case Toolkit::TextField::Property::TEXT: + { + if( impl.mController ) + { + std::string text; + impl.mController->GetText( text ); + value = text; + } + break; + } case Toolkit::TextField::Property::PLACEHOLDER_TEXT: { if( impl.mController ) { std::string text; - impl.mController->GetPlaceholderText( text ); + impl.mController->GetPlaceholderText( PLACEHOLDER_TYPE_INACTIVE, text ); value = text; } break; } - case Toolkit::TextField::Property::TEXT: + case Toolkit::TextField::Property::PLACEHOLDER_TEXT_FOCUSED: { if( impl.mController ) { std::string text; - impl.mController->GetText( text ); + impl.mController->GetPlaceholderText( PLACEHOLDER_TYPE_ACTIVE, text ); value = text; } break; @@ -501,6 +535,14 @@ Property::Value TextField::GetProperty( BaseObject* object, Property::Index inde } break; } + case Toolkit::TextField::Property::PLACEHOLDER_TEXT_COLOR: + { + if ( impl.mController ) + { + value = impl.mController->GetPlaceholderTextColor(); + } + break; + } case Toolkit::TextField::Property::SHADOW_OFFSET: { if ( impl.mController ) @@ -840,9 +882,10 @@ void TextField::OnTap( const TapGesture& gesture ) VirtualKeyboard::Show(); } - SetKeyInputFocus(); - + // Deliver the tap before the focus event to controller; this allows us to detect when focus is gained due to tap-gestures mController->TapEvent( gesture.numberOfTaps, gesture.localPoint.x, gesture.localPoint.y ); + + SetKeyInputFocus(); } void TextField::OnPan( const PanGesture& gesture ) @@ -852,9 +895,11 @@ void TextField::OnPan( const PanGesture& gesture ) bool TextField::OnKeyEvent( const KeyEvent& event ) { - if( Dali::DALI_KEY_ESCAPE == event.keyCode ) + if( Dali::DALI_KEY_ESCAPE == event.keyCode || + "Return" == event.keyPressedName ) // Make a Dali key code for this { ClearKeyInputFocus(); + return true; } return mController->KeyEvent( event ); @@ -862,24 +907,54 @@ bool TextField::OnKeyEvent( const KeyEvent& event ) ImfManager::ImfCallbackData TextField::OnImfEvent( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent ) { + bool update( false ); + + std::string text; + unsigned int cursorPosition( 0 ); + switch ( imfEvent.eventName ) { case ImfManager::COMMIT: { - KeyEvent event( "", imfEvent.predictiveString, 0, 0, 0, KeyEvent::Down ); - mController->KeyEvent( event ); + mController->InsertText( imfEvent.predictiveString, Text::Controller::COMMIT ); + break; + } + case ImfManager::PREEDIT: + { + mController->InsertText( imfEvent.predictiveString, Text::Controller::PRE_EDIT ); + update = true; break; } - case ImfManager::PREEDIT: // fall through case ImfManager::DELETESURROUNDING: + { + mController->RemoveText( imfEvent.cursorOffset, imfEvent.numberOfChars ); + break; + } case ImfManager::GETSURROUNDING: + { + mController->GetText( text ); + cursorPosition = mController->GetLogicalCursorPosition(); + + imfManager.SetSurroundingText( text ); + imfManager.SetCursorPosition( cursorPosition ); + break; + } case ImfManager::VOID: { // do nothing + break; } } // end switch - return ImfManager::ImfCallbackData(); + if( ImfManager::GETSURROUNDING != imfEvent.eventName ) + { + mController->GetText( text ); + cursorPosition = mController->GetLogicalCursorPosition(); + } + + ImfManager::ImfCallbackData callbackData( update, cursorPosition, text, false ); + + return callbackData; } void TextField::RequestTextRelayout() diff --git a/dali-toolkit/internal/text/decorator/text-decorator.cpp b/dali-toolkit/internal/text/decorator/text-decorator.cpp index 871b3d6..6f1cc1a 100644 --- a/dali-toolkit/internal/text/decorator/text-decorator.cpp +++ b/dali-toolkit/internal/text/decorator/text-decorator.cpp @@ -170,7 +170,7 @@ struct Decorator::Impl : public ConnectionTracker struct CursorImpl { CursorImpl() - : color( Dali::Color::WHITE ), + : color( Dali::Color::BLACK ), position(), cursorHeight( 0.0f ), lineHeight( 0.0f ) diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index f7f1739..f39effe 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -21,6 +21,17 @@ // EXTERNAL INCLUDES #include +// INTERNAL INCLUDES +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace { @@ -96,13 +107,19 @@ void GetGlyphsMetrics( GlyphIndex glyphIndex, EventData::EventData( DecoratorPtr decorator ) : mDecorator( decorator ), - mPlaceholderText(), + mPlaceholderTextActive(), + mPlaceholderTextInactive(), + mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ), mEventQueue(), mScrollPosition(), mState( INACTIVE ), mPrimaryCursorPosition( 0u ), mLeftSelectionPosition( 0u ), mRightSelectionPosition( 0u ), + mPreEditStartPosition( 0u ), + mPreEditLength( 0u ), + mIsShowingPlaceholderText( false ), + mPreEditFlag( false ), mDecoratorUpdated( false ), mCursorBlinkEnabled( true ), mGrabHandleEnabled( true ), @@ -221,6 +238,243 @@ bool Controller::Impl::ProcessInputEvents() return mEventData->mDecoratorUpdated; } +void Controller::Impl::ReplaceTextWithPlaceholder() +{ + DALI_ASSERT_DEBUG( mEventData && "No placeholder text available" ); + if( !mEventData ) + { + return; + } + + // Disable handles when showing place-holder text + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); + + const char* text( NULL ); + size_t size( 0 ); + + if( EventData::INACTIVE != mEventData->mState && + 0u != mEventData->mPlaceholderTextActive.c_str() ) + { + text = mEventData->mPlaceholderTextActive.c_str(); + size = mEventData->mPlaceholderTextActive.size(); + } + + else + { + text = mEventData->mPlaceholderTextInactive.c_str(); + size = mEventData->mPlaceholderTextInactive.size(); + } + + // Reset buffers. + mLogicalModel->mText.Clear(); + mLogicalModel->mScriptRuns.Clear(); + mLogicalModel->mFontRuns.Clear(); + mLogicalModel->mLineBreakInfo.Clear(); + mLogicalModel->mWordBreakInfo.Clear(); + mLogicalModel->mBidirectionalParagraphInfo.Clear(); + mLogicalModel->mCharacterDirections.Clear(); + mLogicalModel->mBidirectionalLineInfo.Clear(); + mLogicalModel->mLogicalToVisualMap.Clear(); + mLogicalModel->mVisualToLogicalMap.Clear(); + mVisualModel->mGlyphs.Clear(); + mVisualModel->mGlyphsToCharacters.Clear(); + mVisualModel->mCharactersToGlyph.Clear(); + mVisualModel->mCharactersPerGlyph.Clear(); + mVisualModel->mGlyphsPerCharacter.Clear(); + mVisualModel->mGlyphPositions.Clear(); + mVisualModel->mLines.Clear(); + mVisualModel->ClearCaches(); + mVisualModel->SetTextColor( mEventData->mPlaceholderTextColor ); + + // Convert text into UTF-32 + Vector& utf32Characters = mLogicalModel->mText; + utf32Characters.Resize( size ); + + // This is a bit horrible but std::string returns a (signed) char* + const uint8_t* utf8 = reinterpret_cast( text ); + + // Transform a text array encoded in utf8 into an array encoded in utf32. + // It returns the actual number of characters. + Length characterCount = Utf8ToUtf32( utf8, size, utf32Characters.Begin() ); + utf32Characters.Resize( characterCount ); + + // Reset the cursor position + mEventData->mPrimaryCursorPosition = 0; + + // The natural size needs to be re-calculated. + mRecalculateNaturalSize = true; + + // Apply modifications to the model + mOperationsPending = ALL_OPERATIONS; + UpdateModel( ALL_OPERATIONS ); + mOperationsPending = static_cast( LAYOUT | + ALIGN | + UPDATE_ACTUAL_SIZE | + REORDER ); +} + +void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) +{ + // Calculate the operations to be done. + const OperationsMask operations = static_cast( mOperationsPending & operationsRequired ); + + Vector& utf32Characters = mLogicalModel->mText; + + const Length numberOfCharacters = mLogicalModel->GetNumberOfCharacters(); + + Vector& lineBreakInfo = mLogicalModel->mLineBreakInfo; + 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( numberOfCharacters, TextAbstraction::LINE_NO_BREAK ); + + SetLineBreakInfo( utf32Characters, + lineBreakInfo ); + } + + Vector& wordBreakInfo = mLogicalModel->mWordBreakInfo; + if( GET_WORD_BREAKS & operations ) + { + // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines). + wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK ); + + SetWordBreakInfo( utf32Characters, + wordBreakInfo ); + } + + const bool getScripts = GET_SCRIPTS & operations; + const bool validateFonts = VALIDATE_FONTS & operations; + + Vector& scripts = mLogicalModel->mScriptRuns; + Vector& validFonts = mLogicalModel->mFontRuns; + + 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(); + + if( getScripts ) + { + // Retrieves the scripts used in the text. + multilanguageSupport.SetScripts( utf32Characters, + lineBreakInfo, + scripts ); + } + + if( validateFonts ) + { + if( 0u == validFonts.Count() ) + { + // Copy the requested font defaults received via the property system. + // These may not be valid i.e. may not contain glyphs for the necessary scripts. + GetDefaultFonts( validFonts, numberOfCharacters ); + } + + // 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, + validFonts ); + } + } + + Vector mirroredUtf32Characters; + bool textMirrored = false; + if( BIDI_INFO & operations ) + { + // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's + // bidirectional info. + + Length numberOfParagraphs = 0u; + + const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin(); + for( Length index = 0u; index < numberOfCharacters; ++index ) + { + if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) ) + { + ++numberOfParagraphs; + } + } + + Vector& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo; + bidirectionalInfo.Reserve( numberOfParagraphs ); + + // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts. + SetBidirectionalInfo( utf32Characters, + scripts, + lineBreakInfo, + bidirectionalInfo ); + + if( 0u != bidirectionalInfo.Count() ) + { + // This paragraph has right to left text. Some characters may need to be mirrored. + // TODO: consider if the mirrored string can be stored as well. + + textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters ); + + // Only set the character directions if there is right to left characters. + Vector& directions = mLogicalModel->mCharacterDirections; + directions.Resize( numberOfCharacters ); + + GetCharactersDirection( bidirectionalInfo, + directions ); + } + else + { + // There is no right to left characters. Clear the directions vector. + mLogicalModel->mCharacterDirections.Clear(); + } + + } + + Vector& glyphs = mVisualModel->mGlyphs; + Vector& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters; + Vector& charactersPerGlyph = mVisualModel->mCharactersPerGlyph; + if( SHAPE_TEXT & operations ) + { + const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; + // Shapes the text. + ShapeText( textToShape, + lineBreakInfo, + scripts, + validFonts, + glyphs, + glyphsToCharactersMap, + charactersPerGlyph ); + + // Create the 'number of glyphs' per character and the glyph to character conversion tables. + mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters ); + mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters ); + } + + const Length numberOfGlyphs = glyphs.Count(); + + if( GET_GLYPH_METRICS & operations ) + { + mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs ); + } +} + +void Controller::Impl::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) +{ + if( mFontDefaults ) + { + FontRun fontRun; + fontRun.characterRun.characterIndex = 0; + fontRun.characterRun.numberOfCharacters = numberOfCharacters; + fontRun.fontId = mFontDefaults->GetFontId( mFontClient ); + fontRun.isDefault = true; + + fonts.PushBack( fontRun ); + } +} + void Controller::Impl::OnKeyboardFocus( bool hasFocus ) { if( NULL == mEventData ) @@ -276,16 +530,6 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) mEventData->mScrollAfterUpdateCursorPosition = true; } -void Controller::Impl::HandleCursorKey( int keyCode ) -{ - // TODO - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - return; - } -} - void Controller::Impl::OnTapEvent( const Event& event ) { if( NULL == mEventData ) @@ -298,6 +542,17 @@ void Controller::Impl::OnTapEvent( const Event& event ) if( 1u == tapCount ) { + // Grab handle is not shown until a tap is received whilst EDITING + if( EventData::EDITING == mEventData->mState && + !IsShowingPlaceholderText() ) + { + if( mEventData->mGrabHandleEnabled ) + { + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + } + mEventData->mDecorator->SetPopupActive( false ); + } + ChangeState( EventData::EDITING ); const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; @@ -485,8 +740,23 @@ void Controller::Impl::ChangeState( EventData::State newState ) if( mEventData->mState != newState ) { + // Show different placeholder when switching between active & inactive + bool updatePlaceholder( false ); + if( IsShowingPlaceholderText() && + ( EventData::INACTIVE == newState || + EventData::INACTIVE == mEventData->mState ) ) + { + updatePlaceholder = true; + } + mEventData->mState = newState; + if( updatePlaceholder ) + { + ReplaceTextWithPlaceholder(); + mEventData->mDecoratorUpdated = true; + } + if( EventData::INACTIVE == mEventData->mState ) { mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); @@ -513,14 +783,8 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->StartCursorBlink(); } - if( mEventData->mGrabHandleEnabled ) - { - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); - } - if( mEventData->mGrabHandlePopupEnabled ) - { - mEventData->mDecorator->SetPopupActive( false ); - } + // Grab handle is not shown until a tap is received whilst EDITING + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); mEventData->mDecoratorUpdated = true; @@ -532,7 +796,8 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->StartCursorBlink(); } - if( mEventData->mGrabHandleEnabled ) + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); + if( mEventData->mSelectionEnabled ) { mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); @@ -541,8 +806,6 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->SetPopupActive( true ); } - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); mEventData->mDecoratorUpdated = true; } } diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 51927a6..9295d5f 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -20,6 +20,7 @@ // EXTERNAL INCLUDES #include +#include // INTERNAL INCLUDES #include @@ -109,7 +110,9 @@ struct EventData ~EventData(); DecoratorPtr mDecorator; ///< Pointer to the decorator - std::string mPlaceholderText; ///< The plaxe holder text + std::string mPlaceholderTextActive; ///< The text to display when the TextField is empty with key-input focus + std::string mPlaceholderTextInactive; ///< The text to display when the TextField is empty and inactive + Vector4 mPlaceholderTextColor; ///< The in/active placeholder text color /** * This is used to delay handling events until after the model has been updated. @@ -129,30 +132,35 @@ struct EventData CharacterIndex mLeftSelectionPosition; ///< Index into logical model for left selection handle. CharacterIndex mRightSelectionPosition; ///< Index into logical model for right selection handle. - 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. - bool mUpdateLeftSelectionPosition : 1; ///< True if the visual position of the left selection handle must be recalculated. - bool mUpdateRightSelectionPosition : 1; ///< True if the visual position of the right selection handle must be recalculated. - bool mScrollAfterUpdateCursorPosition : 1; ///< Whether to scroll after the cursor position is updated. + CharacterIndex mPreEditStartPosition; ///< Used to remove the pre-edit text if necessary. + Length mPreEditLength; ///< Used to remove the pre-edit text if necessary. + + bool mIsShowingPlaceholderText : 1; ///< True if the place-holder text is being displayed. + bool mPreEditFlag : 1; ///< True if the model contains text in pre-edit state. + 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. + bool mUpdateLeftSelectionPosition : 1; ///< True if the visual position of the left selection handle must be recalculated. + bool mUpdateRightSelectionPosition : 1; ///< True if the visual position of the right selection handle must be recalculated. + bool mScrollAfterUpdateCursorPosition : 1; ///< Whether to scroll after the cursor position is updated. }; struct ModifyEvent { enum Type { - REPLACE_TEXT, ///< Replace the entire text - INSERT_TEXT, ///< Insert characters at the current cursor position - DELETE_TEXT ///< Delete a character at the current cursor position + PLACEHOLDER_TEXT, ///< Show the placeholder text if necessary + TEXT_REPLACED, ///< The entire text was replaced + TEXT_INSERTED, ///< Insert characters at the current cursor position + TEXT_DELETED ///< Characters were deleted }; Type type; - std::string text; }; struct FontDefaults @@ -193,6 +201,7 @@ struct Controller::Impl mLayoutEngine(), mModifyEvents(), mControlSize(), + mTextColor( Color::BLACK ), mAlignmentOffset(), mOperationsPending( NO_OPERATION ), mMaximumNumberOfCharacters( 50 ), @@ -206,9 +215,6 @@ struct Controller::Impl mView.SetVisualModel( mVisualModel ); // Set the text properties to default - mVisualModel->SetTextColor( Color::WHITE ); - mVisualModel->SetShadowOffset( Vector2::ZERO ); - mVisualModel->SetShadowColor( Color::BLACK ); mVisualModel->SetUnderlineEnabled( false ); mVisualModel->SetUnderlineHeight( 0.0f ); } @@ -224,16 +230,93 @@ struct Controller::Impl void RequestRelayout(); /** + * @brief Request a relayout using the ControlInterface. + */ + void QueueModifyEvent( ModifyEvent::Type type ) + { + ModifyEvent event; + event.type = type; + mModifyEvents.push_back( event ); + + // The event will be processed during relayout + RequestRelayout(); + } + + /** * @brief Helper to move the cursor, grab handle etc. */ bool ProcessInputEvents(); + /** + * @brief Helper to check whether any place-holder text is available. + */ + bool IsPlaceholderAvailable() const + { + return ( mEventData && + ( !mEventData->mPlaceholderTextInactive.empty() || + !mEventData->mPlaceholderTextActive.empty() ) + ); + } + + bool IsShowingPlaceholderText() const + { + return ( mEventData && mEventData->mIsShowingPlaceholderText ); + } + + void ShowPlaceholderText() + { + if( IsPlaceholderAvailable() ) + { + mEventData->mIsShowingPlaceholderText = true; + + // Placeholder-text is dependent on focus state i.e. replace after event processing + QueueModifyEvent( ModifyEvent::PLACEHOLDER_TEXT ); + } + } + + /** + * @brief Called when placeholder-text is hidden + */ + void PlaceholderCleared() + { + if( mEventData ) + { + mEventData->mIsShowingPlaceholderText = false; + + // Remove mPlaceholderTextColor + mVisualModel->SetTextColor( mTextColor ); + } + } + + void PreEditReset() + { + // Reset incase we are in a pre-edit state. + ImfManager imfManager = ImfManager::Get(); + if ( imfManager ) + { + imfManager.Reset(); // Will trigger a commit message + } + } + + /** + * @brief Called when placeholder-text is shown + */ + void ReplaceTextWithPlaceholder(); + + void UpdateModel( OperationsMask operationsRequired ); + + /** + * @brief Retrieve the default fonts. + * + * @param[out] fonts The default font family, style and point sizes. + * @param[in] numberOfCharacters The number of characters in the logical model. + */ + void GetDefaultFonts( Dali::Vector& fonts, Length numberOfCharacters ); + void OnKeyboardFocus( bool hasFocus ); void OnCursorKeyEvent( const Event& event ); - void HandleCursorKey( int keyCode ); - void OnTapEvent( const Event& event ); void OnPanEvent( const Event& event ); @@ -328,6 +411,7 @@ struct Controller::Impl LayoutEngine mLayoutEngine; ///< The layout engine. std::vector mModifyEvents; ///< Temporary stores the text set until the next relayout. Size mControlSize; ///< The size of the control. + Vector4 mTextColor; ///< The regular text color 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. Length mMaximumNumberOfCharacters; ///< Maximum number of characters that can be inserted. diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index d599bdd..a26f5f5 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -20,6 +20,7 @@ // EXTERNAL INCLUDES #include +#include #include // INTERNAL INCLUDES @@ -62,47 +63,104 @@ void Controller::SetText( const std::string& text ) // Cancel previously queued inserts etc. mImpl->mModifyEvents.clear(); - // Keep until size negotiation - ModifyEvent event; - event.type = ModifyEvent::REPLACE_TEXT; - event.text = text; - mImpl->mModifyEvents.push_back( event ); + // Remove the previously set text + ResetText(); + + if( ! text.empty() ) + { + // Convert text into UTF-32 + Vector& utf32Characters = mImpl->mLogicalModel->mText; + 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() ); + + // Transform a text array encoded in utf8 into an array encoded in utf32. + // It returns the actual number of characters. + Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() ); + utf32Characters.Resize( characterCount ); + + // Reset the cursor position + if( mImpl->mEventData ) + { + mImpl->mEventData->mPrimaryCursorPosition = characterCount; + } + + // Update the rest of the model during size negotiation + mImpl->QueueModifyEvent( ModifyEvent::TEXT_REPLACED ); + } + else + { + mImpl->ShowPlaceholderText(); + } if( mImpl->mEventData ) { // Cancel previously queued events mImpl->mEventData->mEventQueue.clear(); - - // TODO - Hide selection decorations } + + // Reset keyboard as text changed + mImpl->PreEditReset(); } void Controller::GetText( std::string& text ) const { - if( !mImpl->mModifyEvents.empty() && - ModifyEvent::REPLACE_TEXT == mImpl->mModifyEvents[0].type ) + if( ! mImpl->IsShowingPlaceholderText() ) { - text = mImpl->mModifyEvents[0].text; + Vector& utf32Characters = mImpl->mLogicalModel->mText; + + if( 0u != utf32Characters.Count() ) + { + uint32_t numberOfBytes = GetNumberOfUtf8Bytes( &utf32Characters[0], utf32Characters.Count() ); + + text.resize( numberOfBytes ); + + // This is a bit horrible but std::string returns a (signed) char* + Utf32ToUtf8( &utf32Characters[0], utf32Characters.Count(), reinterpret_cast(&text[0]) ); + } } - else +} + +unsigned int Controller::GetLogicalCursorPosition() const +{ + if( mImpl->mEventData ) { - // TODO - Convert from UTF-32 + return mImpl->mEventData->mPrimaryCursorPosition; } + + return 0u; } -void Controller::SetPlaceholderText( const std::string& text ) +void Controller::SetPlaceholderText( PlaceholderType type, const std::string& text ) { - if( !mImpl->mEventData ) + if( mImpl->mEventData ) { - mImpl->mEventData->mPlaceholderText = text; + if( PLACEHOLDER_TYPE_INACTIVE == type ) + { + mImpl->mEventData->mPlaceholderTextInactive = text; + } + else + { + mImpl->mEventData->mPlaceholderTextActive = text; + } + + mImpl->ShowPlaceholderText(); } } -void Controller::GetPlaceholderText( std::string& text ) const +void Controller::GetPlaceholderText( PlaceholderType type, std::string& text ) const { - if( !mImpl->mEventData ) + if( mImpl->mEventData ) { - text = mImpl->mEventData->mPlaceholderText; + if( PLACEHOLDER_TYPE_INACTIVE == type ) + { + text = mImpl->mEventData->mPlaceholderTextInactive; + } + else + { + text = mImpl->mEventData->mPlaceholderTextActive; + } } } @@ -227,28 +285,83 @@ float Controller::GetDefaultPointSize() const return 0.0f; } -void Controller::GetDefaultFonts( Vector& fonts, Length numberOfCharacters ) const +void Controller::SetTextColor( const Vector4& textColor ) { - if( mImpl->mFontDefaults ) + mImpl->mTextColor = textColor; + + if( ! mImpl->IsShowingPlaceholderText() ) { - FontRun fontRun; - fontRun.characterRun.characterIndex = 0; - fontRun.characterRun.numberOfCharacters = numberOfCharacters; - fontRun.fontId = mImpl->mFontDefaults->GetFontId( mImpl->mFontClient ); - fontRun.isDefault = true; + mImpl->mVisualModel->SetTextColor( textColor ); + } +} + +const Vector4& Controller::GetTextColor() const +{ + return mImpl->mTextColor; +} + +bool Controller::RemoveText( int cursorOffset, int numberOfChars ) +{ + bool removed( false ); + + if( ! mImpl->IsShowingPlaceholderText() ) + { + // Delete at current cursor position + Vector& currentText = mImpl->mLogicalModel->mText; + CharacterIndex& oldCursorIndex = mImpl->mEventData->mPrimaryCursorPosition; + + CharacterIndex cursorIndex = oldCursorIndex; + + // Validate the cursor position & number of characters + if( std::abs( cursorOffset ) <= cursorIndex ) + { + cursorIndex = oldCursorIndex + cursorOffset; + } + + if( (cursorIndex + numberOfChars) > currentText.Count() ) + { + numberOfChars = currentText.Count() - cursorIndex; + } + + if( cursorIndex >= 0 && + (cursorIndex + numberOfChars) <= currentText.Count() ) + { + Vector::Iterator first = currentText.Begin() + cursorIndex; + Vector::Iterator last = first + numberOfChars; - fonts.PushBack( fontRun ); + currentText.Erase( first, last ); + + // Cursor position retreat + oldCursorIndex = cursorIndex; + + removed = true; + } } + + return removed; } -void Controller::SetTextColor( const Vector4& textColor ) +void Controller::SetPlaceholderTextColor( const Vector4& textColor ) { - mImpl->mVisualModel->SetTextColor( textColor ); + if( mImpl->mEventData ) + { + mImpl->mEventData->mPlaceholderTextColor = textColor; + } + + if( mImpl->IsShowingPlaceholderText() ) + { + mImpl->mVisualModel->SetTextColor( textColor ); + } } -const Vector4& Controller::GetTextColor() const +const Vector4& Controller::GetPlaceholderTextColor() const { - return mImpl->mVisualModel->GetTextColor(); + if( mImpl->mEventData ) + { + return mImpl->mEventData->mPlaceholderTextColor; + } + + return Color::BLACK; } void Controller::SetShadowOffset( const Vector2& shadowOffset ) @@ -369,7 +482,7 @@ Vector3 Controller::GetNaturalSize() SHAPE_TEXT | GET_GLYPH_METRICS ); // Make sure the model is up-to-date before layouting - UpdateModel( onlyOnceOperations ); + mImpl->UpdateModel( onlyOnceOperations ); // Operations that need to be done if the size changes. const OperationsMask sizeOperations = static_cast( LAYOUT | @@ -419,7 +532,7 @@ float Controller::GetHeightForWidth( float width ) SHAPE_TEXT | GET_GLYPH_METRICS ); // Make sure the model is up-to-date before layouting - UpdateModel( onlyOnceOperations ); + mImpl->UpdateModel( onlyOnceOperations ); // Operations that need to be done if the size changes. const OperationsMask sizeOperations = static_cast( LAYOUT | @@ -473,7 +586,7 @@ bool Controller::Relayout( const Size& size ) // Make sure the model is up-to-date before layouting ProcessModifyEvents(); - UpdateModel( mImpl->mOperationsPending ); + mImpl->UpdateModel( mImpl->mOperationsPending ); Size layoutSize; bool updated = DoRelayout( mImpl->mControlSize, @@ -501,20 +614,33 @@ void Controller::ProcessModifyEvents() for( unsigned int i=0; imLogicalModel->mText.Count() && + mImpl->IsShowingPlaceholderText() ) + { + mImpl->ReplaceTextWithPlaceholder(); + } + } + else 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 REPLACE event" ); + DALI_ASSERT_DEBUG( 0 == i && "Unexpected TEXT_REPLACED event" ); - ReplaceTextEvent( events[0].text ); + TextReplacedEvent(); } - else if( ModifyEvent::INSERT_TEXT == events[0].type ) + else if( ModifyEvent::TEXT_INSERTED == events[0].type ) { - InsertTextEvent( events[0].text ); + TextInsertedEvent(); } - else if( ModifyEvent::DELETE_TEXT == events[0].type ) + else if( ModifyEvent::TEXT_DELETED == events[0].type ) { - DeleteTextEvent(); + // Placeholder-text cannot be deleted + if( !mImpl->IsShowingPlaceholderText() ) + { + TextDeletedEvent(); + } } } @@ -522,7 +648,7 @@ void Controller::ProcessModifyEvents() events.clear(); } -void Controller::ReplaceTextEvent( const std::string& text ) +void Controller::ResetText() { // Reset buffers. mImpl->mLogicalModel->mText.Clear(); @@ -544,40 +670,58 @@ void Controller::ReplaceTextEvent( const std::string& text ) mImpl->mVisualModel->mLines.Clear(); mImpl->mVisualModel->ClearCaches(); - // Convert text into UTF-32 - Vector& utf32Characters = mImpl->mLogicalModel->mText; - 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() ); - - // Transform a text array encoded in utf8 into an array encoded in utf32. - // It returns the actual number of characters. - Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() ); - utf32Characters.Resize( characterCount ); - // Reset the cursor position if( mImpl->mEventData ) { - mImpl->mEventData->mPrimaryCursorPosition = characterCount; - // TODO - handle secondary cursor + 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. + mImpl->mLogicalModel->mScriptRuns.Clear(); + mImpl->mLogicalModel->mFontRuns.Clear(); + mImpl->mLogicalModel->mLineBreakInfo.Clear(); + mImpl->mLogicalModel->mWordBreakInfo.Clear(); + mImpl->mLogicalModel->mBidirectionalParagraphInfo.Clear(); + mImpl->mLogicalModel->mCharacterDirections.Clear(); + mImpl->mLogicalModel->mBidirectionalLineInfo.Clear(); + mImpl->mLogicalModel->mLogicalToVisualMap.Clear(); + mImpl->mLogicalModel->mVisualToLogicalMap.Clear(); + mImpl->mVisualModel->mGlyphs.Clear(); + mImpl->mVisualModel->mGlyphsToCharacters.Clear(); + mImpl->mVisualModel->mCharactersToGlyph.Clear(); + mImpl->mVisualModel->mCharactersPerGlyph.Clear(); + mImpl->mVisualModel->mGlyphsPerCharacter.Clear(); + mImpl->mVisualModel->mGlyphPositions.Clear(); + mImpl->mVisualModel->mLines.Clear(); + mImpl->mVisualModel->ClearCaches(); + // The natural size needs to be re-calculated. mImpl->mRecalculateNaturalSize = true; // Apply modifications to the model mImpl->mOperationsPending = ALL_OPERATIONS; - UpdateModel( ALL_OPERATIONS ); + mImpl->UpdateModel( ALL_OPERATIONS ); mImpl->mOperationsPending = static_cast( LAYOUT | ALIGN | UPDATE_ACTUAL_SIZE | REORDER ); } -void Controller::InsertTextEvent( const std::string& text ) +void Controller::TextInsertedEvent() { - DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected InsertTextEvent" ); + DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextInsertedEvent" ); // TODO - Optimize this mImpl->mLogicalModel->mScriptRuns.Clear(); @@ -598,45 +742,12 @@ void Controller::InsertTextEvent( const std::string& text ) mImpl->mVisualModel->mLines.Clear(); mImpl->mVisualModel->ClearCaches(); - // 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() ); - - // Transform a text array encoded in utf8 into an array encoded in utf32. - // It returns the actual number of characters. - Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() ); - utf32Characters.Resize( characterCount ); - - const Length numberOfCharactersInModel = mImpl->mLogicalModel->GetNumberOfCharacters(); - - // Restrict new text to fit within Maximum characters setting - Length maxSizeOfNewText = std::min ( ( mImpl->mMaximumNumberOfCharacters - numberOfCharactersInModel ), characterCount ); - - // Insert at current cursor position - CharacterIndex& cursorIndex = mImpl->mEventData->mPrimaryCursorPosition; - - Vector& modifyText = mImpl->mLogicalModel->mText; - - if( cursorIndex < numberOfCharactersInModel ) - { - modifyText.Insert( modifyText.Begin() + cursorIndex, utf32Characters.Begin(), utf32Characters.Begin()+ maxSizeOfNewText ); - } - else - { - modifyText.Insert( modifyText.End(), utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText ); - } - - cursorIndex += maxSizeOfNewText; - // The natural size needs to be re-calculated. mImpl->mRecalculateNaturalSize = true; // Apply modifications to the model; TODO - Optimize this mImpl->mOperationsPending = ALL_OPERATIONS; - UpdateModel( ALL_OPERATIONS ); + mImpl->UpdateModel( ALL_OPERATIONS ); mImpl->mOperationsPending = static_cast( LAYOUT | ALIGN | UPDATE_ACTUAL_SIZE | @@ -645,16 +756,11 @@ void Controller::InsertTextEvent( const std::string& text ) // Queue a cursor reposition event; this must wait until after DoRelayout() mImpl->mEventData->mUpdateCursorPosition = true; mImpl->mEventData->mScrollAfterUpdateCursorPosition = true; - - if ( characterCount > maxSizeOfNewText ) - { - mImpl->mControlInterface.MaxLengthReached(); - } } -void Controller::DeleteTextEvent() +void Controller::TextDeletedEvent() { - DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected InsertTextEvent" ); + DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected TextDeletedEvent" ); // TODO - Optimize this mImpl->mLogicalModel->mScriptRuns.Clear(); @@ -675,25 +781,12 @@ void Controller::DeleteTextEvent() mImpl->mVisualModel->mLines.Clear(); mImpl->mVisualModel->ClearCaches(); - // Delte at current cursor position - Vector& modifyText = mImpl->mLogicalModel->mText; - CharacterIndex& cursorIndex = mImpl->mEventData->mPrimaryCursorPosition; - - if( cursorIndex > 0 && - cursorIndex-1 < modifyText.Count() ) - { - modifyText.Remove( modifyText.Begin() + cursorIndex - 1 ); - - // Cursor position retreat - --cursorIndex; - } - // The natural size needs to be re-calculated. mImpl->mRecalculateNaturalSize = true; // Apply modifications to the model; TODO - Optimize this mImpl->mOperationsPending = ALL_OPERATIONS; - UpdateModel( ALL_OPERATIONS ); + mImpl->UpdateModel( ALL_OPERATIONS ); mImpl->mOperationsPending = static_cast( LAYOUT | ALIGN | UPDATE_ACTUAL_SIZE | @@ -704,152 +797,6 @@ void Controller::DeleteTextEvent() mImpl->mEventData->mScrollAfterUpdateCursorPosition = true; } -void Controller::UpdateModel( OperationsMask operationsRequired ) -{ - // Calculate the operations to be done. - const OperationsMask operations = static_cast( mImpl->mOperationsPending & operationsRequired ); - - Vector& utf32Characters = mImpl->mLogicalModel->mText; - - const Length numberOfCharacters = mImpl->mLogicalModel->GetNumberOfCharacters(); - - Vector& lineBreakInfo = mImpl->mLogicalModel->mLineBreakInfo; - 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( numberOfCharacters, TextAbstraction::LINE_NO_BREAK ); - - SetLineBreakInfo( utf32Characters, - lineBreakInfo ); - } - - Vector& wordBreakInfo = mImpl->mLogicalModel->mWordBreakInfo; - if( GET_WORD_BREAKS & operations ) - { - // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines). - wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK ); - - SetWordBreakInfo( utf32Characters, - wordBreakInfo ); - } - - const bool getScripts = GET_SCRIPTS & operations; - const bool validateFonts = VALIDATE_FONTS & operations; - - Vector& scripts = mImpl->mLogicalModel->mScriptRuns; - Vector& validFonts = mImpl->mLogicalModel->mFontRuns; - - 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(); - - if( getScripts ) - { - // Retrieves the scripts used in the text. - multilanguageSupport.SetScripts( utf32Characters, - lineBreakInfo, - scripts ); - } - - if( validateFonts ) - { - if( 0u == validFonts.Count() ) - { - // Copy the requested font defaults received via the property system. - // These may not be valid i.e. may not contain glyphs for the necessary scripts. - GetDefaultFonts( validFonts, numberOfCharacters ); - } - - // 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, - validFonts ); - } - } - - Vector mirroredUtf32Characters; - bool textMirrored = false; - if( BIDI_INFO & operations ) - { - // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's - // bidirectional info. - - Length numberOfParagraphs = 0u; - - const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin(); - for( Length index = 0u; index < numberOfCharacters; ++index ) - { - if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) ) - { - ++numberOfParagraphs; - } - } - - Vector& bidirectionalInfo = mImpl->mLogicalModel->mBidirectionalParagraphInfo; - bidirectionalInfo.Reserve( numberOfParagraphs ); - - // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts. - SetBidirectionalInfo( utf32Characters, - scripts, - lineBreakInfo, - bidirectionalInfo ); - - if( 0u != bidirectionalInfo.Count() ) - { - // This paragraph has right to left text. Some characters may need to be mirrored. - // TODO: consider if the mirrored string can be stored as well. - - textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters ); - - // Only set the character directions if there is right to left characters. - Vector& directions = mImpl->mLogicalModel->mCharacterDirections; - directions.Resize( numberOfCharacters ); - - GetCharactersDirection( bidirectionalInfo, - directions ); - } - else - { - // There is no right to left characters. Clear the directions vector. - mImpl->mLogicalModel->mCharacterDirections.Clear(); - } - - } - - Vector& glyphs = mImpl->mVisualModel->mGlyphs; - Vector& glyphsToCharactersMap = mImpl->mVisualModel->mGlyphsToCharacters; - Vector& charactersPerGlyph = mImpl->mVisualModel->mCharactersPerGlyph; - if( SHAPE_TEXT & operations ) - { - const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; - // Shapes the text. - ShapeText( textToShape, - lineBreakInfo, - scripts, - validFonts, - glyphs, - glyphsToCharactersMap, - charactersPerGlyph ); - - // Create the 'number of glyphs' per character and the glyph to character conversion tables. - mImpl->mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters ); - mImpl->mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters ); - } - - const Length numberOfGlyphs = glyphs.Count(); - - if( GET_GLYPH_METRICS & operations ) - { - mImpl->mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs ); - } -} - bool Controller::DoRelayout( const Size& size, OperationsMask operationsRequired, Size& layoutSize ) @@ -1115,18 +1062,24 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent ) } else if( Dali::DALI_KEY_BACKSPACE == keyCode ) { - // Queue a delete event - ModifyEvent event; - event.type = ModifyEvent::DELETE_TEXT; - mImpl->mModifyEvents.push_back( event ); + // Remove the character before the current cursor position + bool removed = RemoveText( -1, 1 ); + + if( removed ) + { + if( 0u == mImpl->mLogicalModel->mText.Count() ) + { + mImpl->ShowPlaceholderText(); + } + else + { + mImpl->QueueModifyEvent( ModifyEvent::TEXT_DELETED ); + } + } } - else if( !keyString.empty() ) + else { - // Queue an insert event - ModifyEvent event; - event.type = ModifyEvent::INSERT_TEXT; - event.text = keyString; - mImpl->mModifyEvents.push_back( event ); + InsertText( keyString, COMMIT ); } mImpl->ChangeState( EventData::EDITING ); // todo Confirm this is the best place to change the state of @@ -1137,6 +1090,96 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent ) return false; } +void Controller::InsertText( const std::string& text, Controller::InsertType type ) +{ + bool removedPreEdit( false ); + bool maxLengthReached( false ); + + if( ! text.empty() ) + { + if( mImpl->IsShowingPlaceholderText() ) + { + ResetText(); + } + } + + if( mImpl->mEventData ) + { + if( COMMIT == type ) + { + mImpl->mEventData->mPreEditFlag = false; + } + else // PRE_EDIT + { + if( mImpl->mEventData->mPreEditFlag && + 0 != mImpl->mEventData->mPreEditLength ) + { + // Remove previous pre-edit text + mImpl->mEventData->mPrimaryCursorPosition = mImpl->mEventData->mPreEditStartPosition; + removedPreEdit = RemoveText( -1, mImpl->mEventData->mPreEditLength ); + } + else + { + // Record the start of the pre-edit text + mImpl->mEventData->mPreEditStartPosition = mImpl->mEventData->mPrimaryCursorPosition; + mImpl->mEventData->mPreEditLength = text.size(); + } + + mImpl->mEventData->mPreEditFlag = true; + } + } + + if( ! text.empty() ) + { + // 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() ); + + // Transform a text array encoded in utf8 into an array encoded in utf32. + // It returns the actual number of characters. + Length characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() ); + utf32Characters.Resize( characterCount ); + + const Length numberOfCharactersInModel = mImpl->mLogicalModel->GetNumberOfCharacters(); + + // Restrict new text to fit within Maximum characters setting + Length maxSizeOfNewText = std::min ( ( mImpl->mMaximumNumberOfCharacters - numberOfCharactersInModel ), characterCount ); + maxLengthReached = ( characterCount > maxSizeOfNewText ); + + // Insert at current cursor position + CharacterIndex& cursorIndex = mImpl->mEventData->mPrimaryCursorPosition; + + Vector& modifyText = mImpl->mLogicalModel->mText; + + if( cursorIndex < numberOfCharactersInModel ) + { + modifyText.Insert( modifyText.Begin() + cursorIndex, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText ); + } + else + { + modifyText.Insert( modifyText.End(), utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText ); + } + + cursorIndex += maxSizeOfNewText; + } + + if( removedPreEdit || !text.empty() ) + { + // Queue an inserted event + mImpl->QueueModifyEvent( ModifyEvent::TEXT_INSERTED ); + } + + if( maxLengthReached ) + { + mImpl->mControlInterface.MaxLengthReached(); + + mImpl->PreEditReset(); + } +} + void Controller::TapEvent( unsigned int tapCount, float x, float y ) { DALI_ASSERT_DEBUG( mImpl->mEventData && "Unexpected TapEvent" ); @@ -1151,6 +1194,9 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y ) mImpl->RequestRelayout(); } + + // Reset keyboard as tap event has occurred. + mImpl->PreEditReset(); } void Controller::PanEvent( Gesture::State state, const Vector2& displacement ) diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index 5ef498d..4edd281 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -50,6 +50,15 @@ typedef IntrusivePtr ControllerPtr; typedef Dali::Toolkit::Text::ControlInterface ControlInterface; /** + * @brief Different placeholder-text can be shown when the control is active/inactive. + */ +enum PlaceholderType +{ + PLACEHOLDER_TYPE_ACTIVE, + PLACEHOLDER_TYPE_INACTIVE, +}; + +/** * @brief A Text Controller is used by UI Controls which display text. * * It manipulates the Logical & Visual text models on behalf of the UI Controls. @@ -60,7 +69,7 @@ typedef Dali::Toolkit::Text::ControlInterface ControlInterface; */ class Controller : public RefObject, public Decorator::Observer { -private: +public: /** * @brief Text related operations to be done in the relayout process. @@ -83,7 +92,14 @@ private: ALL_OPERATIONS = 0xFFFF }; -public: + /** + * @brief Used to distinguish between regular key events and IMF events + */ + enum InsertType + { + COMMIT, + PRE_EDIT + }; /** * @brief Create a new instance of a Controller. @@ -111,16 +127,34 @@ public: /** * @brief Replaces any placeholder text previously set. * + * @param[in] cursorOffset Start position from the current cursor position to start deleting characters. + * @param[in] numberOfChars The number of characters to delete from the cursorOffset. + * @return True if the remove was successful. + */ + bool RemoveText( int cursorOffset, int numberOfChars ); + + /** + * @brief Retrieve the current cursor position. + * + * @return The cursor position. + */ + unsigned int GetLogicalCursorPosition() const; + + /** + * @brief Replaces any placeholder text previously set. + * + * @param[in] type Different placeholder-text can be shown when the control is active/inactive. * @param[in] text A string of UTF-8 characters. */ - void SetPlaceholderText( const std::string& text ); + void SetPlaceholderText( PlaceholderType type, const std::string& text ); /** * @brief Retrieve any placeholder text previously set. * - * @return A string of UTF-8 characters. + * @param[in] type Different placeholder-text can be shown when the control is active/inactive. + * @param[out] A string of UTF-8 characters. */ - void GetPlaceholderText( std::string& text ) const; + void GetPlaceholderText( PlaceholderType type, std::string& text ) const; /** * @brief Sets the maximum number of characters that can be inserted into the TextModel @@ -179,26 +213,32 @@ public: float GetDefaultPointSize() const; /** - * @brief Retrieve the default fonts. + * @brief Set the text color + * + * @param textColor The text color + */ + void SetTextColor( const Vector4& textColor ); + + /** + * @brief Retrieve the text color * - * @param[out] fonts The default font family, style and point sizes. - * @param[in] numberOfCharacters The number of characters in the logical model. + * @return The text color */ - void GetDefaultFonts( Dali::Vector& fonts, Length numberOfCharacters ) const; + const Vector4& GetTextColor() const; /** * @brief Set the text color * * @param textColor The text color */ - void SetTextColor( const Vector4& textColor ); + void SetPlaceholderTextColor( const Vector4& textColor ); /** * @brief Retrieve the text color * * @return The text color */ - const Vector4& GetTextColor() const; + const Vector4& GetPlaceholderTextColor() const; /** * @brief Set the shadow offset. @@ -332,30 +372,24 @@ public: void ProcessModifyEvents(); /** - * @brief Used to process an event queued from SetText() - * - * @param[in] newText The new text to store in the logical model. + * @brief Used to remove placeholder text. */ - void ReplaceTextEvent( const std::string& newText ); + void ResetText(); /** - * @brief Used to process an event queued from key events etc. - * - * @param[in] text The text to insert into the logical model. + * @brief Used to process an event queued from SetText() */ - void InsertTextEvent( const std::string& text ); + void TextReplacedEvent(); /** - * @brief Used to process an event queued from backspace key etc. + * @brief Used to process an event queued from key events etc. */ - void DeleteTextEvent(); + void TextInsertedEvent(); /** - * @brief Update the model following text replace/insert etc. - * - * @param[in] operationsRequired The layout operations which need to be done. + * @brief Used to process an event queued from backspace key etc. */ - void UpdateModel( OperationsMask operationsRequired ); + void TextDeletedEvent(); /** * @brief Lays-out the text. @@ -407,10 +441,19 @@ public: * @brief Caller by editable UI controls when key events are received. * * @param[in] event The key event. + * @param[in] type Used to distinguish between regular key events and IMF events. */ bool KeyEvent( const Dali::KeyEvent& event ); /** + * @brief Caller by editable UI controls when key events are received. + * + * @param[in] text The text to insert. + * @param[in] type Used to distinguish between regular key events and IMF events. + */ + void InsertText( const std::string& text, InsertType type ); + + /** * @brief Caller by editable UI controls when a tap gesture occurs. * @param[in] tapCount The number of taps. * @param[in] x The x position relative to the top-left of the parent control. diff --git a/dali-toolkit/internal/text/visual-model-impl.cpp b/dali-toolkit/internal/text/visual-model-impl.cpp index 922f309..5f36226 100644 --- a/dali-toolkit/internal/text/visual-model-impl.cpp +++ b/dali-toolkit/internal/text/visual-model-impl.cpp @@ -493,10 +493,10 @@ VisualModel::VisualModel() mGlyphsPerCharacter(), mGlyphPositions(), mLines(), - mTextColor(), - mShadowColor(), - mUnderlineColor(), - mShadowOffset(), + mTextColor( Color::BLACK ), + mShadowColor( Color::BLACK ), + mUnderlineColor( Color::BLACK ), + mShadowOffset( Vector2::ZERO ), mUnderlineHeight( 0.0f ), mNaturalSize(), mActualSize(), diff --git a/dali-toolkit/public-api/controls/text-controls/text-field.h b/dali-toolkit/public-api/controls/text-controls/text-field.h index 0af8dc0..96de982 100644 --- a/dali-toolkit/public-api/controls/text-controls/text-field.h +++ b/dali-toolkit/public-api/controls/text-controls/text-field.h @@ -62,8 +62,9 @@ public: enum { RENDERING_BACKEND = PROPERTY_START_INDEX, ///< name "rendering-backend", The type or rendering e.g. bitmap-based, type INT - PLACEHOLDER_TEXT, ///< name "placeholder-text", The text to display when the TextField is empty, type STRING TEXT, ///< name "text", The text to display in UTF-8 format, type STRING + PLACEHOLDER_TEXT, ///< name "placeholder-text", The text to display when the TextField is empty and inactive, type STRING + PLACEHOLDER_TEXT_FOCUSED, ///< name "placeholder-text-focused", The text to display when the TextField is empty with key-input focus, type STRING FONT_FAMILY, ///< name "font-family", The requested font family, type STRING FONT_STYLE, ///< name "font-style", The requested font style e.g. Regular/Italic, type STRING POINT_SIZE, ///< name "point-size", The size of font in points, type FLOAT @@ -72,6 +73,7 @@ public: HORIZONTAL_ALIGNMENT, ///< name "horizontal-alignment", The line horizontal alignment, type STRING, values "BEGIN", "CENTER", "END" VERTICAL_ALIGNMENT, ///< name "vertical-alignment", The line vertical alignment, type STRING, values "TOP", "CENTER", "BOTTOM" TEXT_COLOR, ///< name "text-color", The text color, type VECTOR4 + PLACEHOLDER_TEXT_COLOR, ///< name "placeholder-text-color", The placeholder-text color, type VECTOR4 SHADOW_OFFSET, ///< name "shadow-offset", The drop shadow offset 0 indicates no shadow, type VECTOR2 SHADOW_COLOR, ///< name "shadow-color", The color of a drop shadow, type VECTOR4 PRIMARY_CURSOR_COLOR, ///< name "primary-cursor-color", The color to apply to the primary cursor, type VECTOR4 -- 2.7.4