X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=621d50ea064f7dc0a53fb0367963ce52d5ddeebd;hp=f77714c712f4698532cb1ef98614ded652a3675a;hb=771ece63d9ad24a6835cbe46c8137db2ced47356;hpb=ebc5e24273f0d9042194e533a09d989ff6f7b196 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index f77714c..621d50e 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -128,14 +128,15 @@ EventData::EventData( DecoratorPtr decorator ) mDecoratorUpdated( false ), mCursorBlinkEnabled( true ), mGrabHandleEnabled( true ), - mGrabHandlePopupEnabled( false ), - mSelectionEnabled( false ), + mGrabHandlePopupEnabled( true ), + mSelectionEnabled( true ), mHorizontalScrollingEnabled( true ), mVerticalScrollingEnabled( false ), mUpdateCursorPosition( false ), mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), - mScrollAfterUpdateCursorPosition( false ) + mScrollAfterUpdatePosition( false ), + mScrollAfterDelete( false ) {} EventData::~EventData() @@ -192,48 +193,73 @@ bool Controller::Impl::ProcessInputEvents() UpdateCursorPosition(); - if( mEventData->mScrollAfterUpdateCursorPosition ) + if( mEventData->mScrollAfterUpdatePosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; + const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); + + ScrollToMakePositionVisible( primaryCursorPosition ); + mEventData->mScrollAfterUpdatePosition = false; } mEventData->mDecoratorUpdated = true; mEventData->mUpdateCursorPosition = false; } - else if( mEventData->mUpdateLeftSelectionPosition ) + else if( mEventData->mScrollAfterDelete ) { - UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); - - if( mEventData->mScrollAfterUpdateCursorPosition ) - { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; - } - + ScrollTextToMatchCursor(); mEventData->mDecoratorUpdated = true; - mEventData->mUpdateLeftSelectionPosition = false; + mEventData->mScrollAfterDelete = false; } - else if( mEventData->mUpdateRightSelectionPosition ) + else { - UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + bool leftScroll = false; + bool rightScroll = false; - if( mEventData->mScrollAfterUpdateCursorPosition ) + if( mEventData->mUpdateLeftSelectionPosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; + UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); + + if( mEventData->mScrollAfterUpdatePosition ) + { + const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE ); + + ScrollToMakePositionVisible( leftHandlePosition ); + leftScroll = true; + } + + mEventData->mDecoratorUpdated = true; + mEventData->mUpdateLeftSelectionPosition = false; } - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateRightSelectionPosition = false; + if( mEventData->mUpdateRightSelectionPosition ) + { + UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + + if( mEventData->mScrollAfterUpdatePosition ) + { + const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE ); + + ScrollToMakePositionVisible( rightHandlePosition ); + rightScroll = true; + } + + mEventData->mDecoratorUpdated = true; + mEventData->mUpdateRightSelectionPosition = false; + } + + if( leftScroll || rightScroll ) + { + mEventData->mScrollAfterUpdatePosition = false; + } } mEventData->mEventQueue.clear(); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" ); - bool decoratorUpdated = mEventData->mDecoratorUpdated; + const bool decoratorUpdated = mEventData->mDecoratorUpdated; mEventData->mDecoratorUpdated = false; + return decoratorUpdated; } @@ -431,7 +457,7 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; - mEventData->mScrollAfterUpdateCursorPosition = true; + mEventData->mScrollAfterUpdatePosition = true; } void Controller::Impl::OnTapEvent( const Event& event ) @@ -456,12 +482,21 @@ void Controller::Impl::OnTapEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; - mEventData->mScrollAfterUpdateCursorPosition = true; + mEventData->mScrollAfterUpdatePosition = true; } else if( mEventData->mSelectionEnabled && ( 2u == tapCount ) ) { - RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat ); + // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords. + const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; + const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; + + RepositionSelectionHandles( xPosition, + yPosition ); + + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; } } } @@ -514,6 +549,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) } const unsigned int state = event.p1.mUint; + const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state ); if( HANDLE_PRESSED == state ) { @@ -525,7 +561,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( Event::GRAB_HANDLE_EVENT == event.type ) { - ChangeState ( EventData::EDITING ); + ChangeState ( EventData::GRAB_HANDLE_PANNING ); if( handleNewPosition != mEventData->mPrimaryCursorPosition ) { @@ -535,45 +571,95 @@ void Controller::Impl::OnHandleEvent( const Event& event ) } else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type ) { + ChangeState ( EventData::SELECTION_HANDLE_PANNING ); + if( handleNewPosition != mEventData->mLeftSelectionPosition ) { mEventData->mLeftSelectionPosition = handleNewPosition; + + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + mEventData->mUpdateLeftSelectionPosition = true; } } else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { + ChangeState ( EventData::SELECTION_HANDLE_PANNING ); + if( handleNewPosition != mEventData->mRightSelectionPosition ) { mEventData->mRightSelectionPosition = handleNewPosition; + + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + mEventData->mUpdateRightSelectionPosition = true; } } - } + } // end ( HANDLE_PRESSED == state ) else if( ( HANDLE_RELEASED == state ) || - ( HANDLE_STOP_SCROLLING == state ) ) + handleStopScrolling ) { - if( mEventData->mGrabHandlePopupEnabled ) + CharacterIndex handlePosition = 0u; + if( handleStopScrolling ) { - ChangeState( EventData::EDITING_WITH_POPUP ); + // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords. + const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; + const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; + + handlePosition = GetClosestCursorIndex( xPosition, yPosition ); } + if( Event::GRAB_HANDLE_EVENT == event.type ) { mEventData->mUpdateCursorPosition = true; - if( HANDLE_STOP_SCROLLING == state ) + ChangeState( EventData::EDITING_WITH_POPUP ); + + if( handleStopScrolling ) { - // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords. - const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; - const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; + mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition; + mEventData->mPrimaryCursorPosition = handlePosition; + } + } + else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::SELECTING ); - mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition ); + if( handleStopScrolling ) + { + mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition; + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition; + mEventData->mLeftSelectionPosition = handlePosition; + + if( mEventData->mUpdateLeftSelectionPosition ) + { + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + } + } + } + else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::SELECTING ); - mEventData->mScrollAfterUpdateCursorPosition = true; + if( handleStopScrolling ) + { + mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition; + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition; + mEventData->mRightSelectionPosition = handlePosition; + + if( mEventData->mUpdateRightSelectionPosition ) + { + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + } } } + mEventData->mDecoratorUpdated = true; - } + } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) ) else if( HANDLE_SCROLLING == state ) { const float xSpeed = event.p2.mFloat; @@ -583,44 +669,151 @@ void Controller::Impl::OnHandleEvent( const Event& event ) ClampHorizontalScroll( actualSize ); - mEventData->mDecoratorUpdated = true; - } + const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type; + const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type; + + if( Event::GRAB_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::GRAB_HANDLE_PANNING ); + } + else if( leftSelectionHandleEvent || rightSelectionHandleEvent ) + { + // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles. + // Think if something can be done to save power. + + ChangeState( EventData::SELECTION_HANDLE_PANNING ); + + const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE ); + + // Get the new handle position. + // The selection handle's position is in decorator coords. Need to transforms to text coords. + const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x, + position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y ); + + if( leftSelectionHandleEvent ) + { + mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition; + mEventData->mLeftSelectionPosition = handlePosition; + } + else + { + mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition; + mEventData->mRightSelectionPosition = handlePosition; + } + + if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) + { + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + } + } + mEventData->mDecoratorUpdated = true; + } // end ( HANDLE_SCROLLING == state ) } -void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) +void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd ) { - if( NULL == mEventData ) + if( selectionStart == selectionEnd ) { - // Nothing to do if there is no text input. + // Nothing to select if handles are in the same place. return; } - // TODO - Find which word was selected + mEventData->mDecorator->ClearHighlights(); - const Vector& glyphs = mVisualModel->mGlyphs; - const Vector::SizeType glyphCount = glyphs.Count(); + mEventData->mLeftSelectionPosition = selectionStart; + mEventData->mRightSelectionPosition = selectionEnd; - const Vector& positions = mVisualModel->mGlyphPositions; - const Vector::SizeType positionCount = positions.Count(); + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); + const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin(); + const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin(); + + // TODO: Better algorithm to create the highlight box. + // TODO: Multi-line. + + const Vector& lines = mVisualModel->mLines; + const LineRun& firstLine = *lines.Begin(); + const float height = firstLine.ascender + -firstLine.descender; + + const bool indicesSwapped = ( selectionStart > selectionEnd ); + if( indicesSwapped ) + { + std::swap( selectionStart, selectionEnd ); + } + + GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); + GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u; + + mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped ); + + const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; + + for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index ) + { + const GlyphInfo& glyph = *( glyphsBuffer + index ); + const Vector2& position = *( positionsBuffer + index ); + + const float xPosition = position.x - glyph.xBearing + offset.x; + mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height ); + } + + CursorInfo primaryCursorInfo; + GetCursorPosition( mEventData->mLeftSelectionPosition, + primaryCursorInfo ); + + CursorInfo secondaryCursorInfo; + GetCursorPosition( mEventData->mRightSelectionPosition, + secondaryCursorInfo ); + + const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset; + const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset; + + mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight ); + + mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight ); + + // Set the flag to update the decorator. + mEventData->mDecoratorUpdated = true; +} - // Guard against glyphs which did not fit inside the layout - const Vector::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount; +void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) +{ + if( NULL == mEventData ) + { + // Nothing to do if there is no text input. + return; + } - if( count ) + if( IsShowingPlaceholderText() ) { - float primaryX = positions[0].x + mEventData->mScrollPosition.x; - float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x; + // Nothing to do if there is the place-holder text. + return; + } - // TODO - multi-line selection - const Vector& lines = mVisualModel->mLines; - float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f; + const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); + const Length numberOfLines = mVisualModel->mLines.Count(); + if( 0 == numberOfGlyphs || + 0 == numberOfLines ) + { + // Nothing to do if there is no text. + return; + } - mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height ); - mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height ); + // Find which word was selected + CharacterIndex selectionStart( 0 ); + CharacterIndex selectionEnd( 0 ); + FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - mEventData->mDecorator->ClearHighlights(); - mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y ); + if( selectionStart == selectionEnd ) + { + ChangeState( EventData::EDITING ); + // Nothing to select. i.e. a white space, out of bounds + return; } + + RepositionSelectionHandles( selectionStart, selectionEnd ); } void Controller::Impl::ChangeState( EventData::State newState ) @@ -652,6 +845,31 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + if( mEventData->mGrabHandlePopupEnabled ) + { + TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY ); + if ( !IsClipboardEmpty() ) + { + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); + } + + mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow ); + mEventData->mDecorator->SetPopupActive( true ); + } + mEventData->mDecoratorUpdated = true; + } + else if ( EventData::SELECTION_CHANGED == mEventData->mState ) + { + if( mEventData->mGrabHandlePopupEnabled ) + { + TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY ); + if ( !IsClipboardEmpty() ) + { + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); + } + mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow ); + mEventData->mDecorator->SetPopupActive( true ); + } mEventData->mDecoratorUpdated = true; } else if( EventData::EDITING == mEventData->mState ) @@ -665,6 +883,10 @@ void Controller::Impl::ChangeState( EventData::State newState ) mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); + if( mEventData->mGrabHandlePopupEnabled ) + { + mEventData->mDecorator->SetPopupActive( false ); + } mEventData->mDecoratorUpdated = true; } else if( EventData::EDITING_WITH_POPUP == mEventData->mState ) @@ -674,18 +896,58 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->StartCursorBlink(); } - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); if( mEventData->mSelectionEnabled ) { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); + } + else + { + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); } if( mEventData->mGrabHandlePopupEnabled ) { + TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL ); + + if ( !IsClipboardEmpty() ) + { + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); + } + + mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow ); mEventData->mDecorator->SetPopupActive( true ); } mEventData->mDecoratorUpdated = true; } + else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState ) + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); + mEventData->mDecorator->StopCursorBlink(); + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + if( mEventData->mGrabHandlePopupEnabled ) + { + mEventData->mDecorator->SetPopupActive( false ); + } + mEventData->mDecoratorUpdated = true; + } + else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + if( mEventData->mCursorBlinkEnabled ) + { + mEventData->mDecorator->StartCursorBlink(); + } + mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true ); + mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false ); + mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false ); + if( mEventData->mGrabHandlePopupEnabled ) + { + mEventData->mDecorator->SetPopupActive( false ); + } + mEventData->mDecoratorUpdated = true; + } } } @@ -707,9 +969,49 @@ LineIndex Controller::Impl::GetClosestLine( float y ) const } } + if( lineIndex == 0 ) + { + return 0; + } + return lineIndex-1; } +void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex ) +{ + CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY ); + if( hitCharacter >= mLogicalModel->mText.Count() ) + { + // Selection out of bounds. + return; + } + + startIndex = hitCharacter; + endIndex = hitCharacter; + + if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) ) + { + // Find the start and end of the text + for( startIndex = hitCharacter; startIndex > 0; --startIndex ) + { + Character charCode = mLogicalModel->mText[ startIndex-1 ]; + if( TextAbstraction::IsWhiteSpace( charCode ) ) + { + break; + } + } + const CharacterIndex pastTheEnd = mLogicalModel->mText.Count(); + for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + { + Character charCode = mLogicalModel->mText[ endIndex ]; + if( TextAbstraction::IsWhiteSpace( charCode ) ) + { + break; + } + } + } +} + CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, float visualY ) { @@ -780,9 +1082,10 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex ); - const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance; + // Find the mid-point of the area containing the glyph + const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance; - if( visualX < glyphX ) + if( visualX < glyphCenter ) { matched = true; break; @@ -796,7 +1099,9 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, visualIndex = endCharacter; } - return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex; + logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex; + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex ); + return logicalIndex; } void Controller::Impl::GetCursorPosition( CharacterIndex logical, @@ -1041,40 +1346,116 @@ void Controller::Impl::UpdateCursorPosition() return; } - CursorInfo cursorInfo; - GetCursorPosition( mEventData->mPrimaryCursorPosition, - cursorInfo ); + if( IsShowingPlaceholderText() ) + { + // Do not want to use the place-holder text to set the cursor position. - const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; - const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; + // Use the line's height of the font's family set to set the cursor's size. + // If there is no font's family set, use the default font. + // Use the current alignment to place the cursor at the beginning, center or end of the box. - // Sets the cursor position. - mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, - cursorPosition.x, - cursorPosition.y, - cursorInfo.primaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y ); + float lineHeight = 0.f; - // Sets the grab handle position. - mEventData->mDecorator->SetPosition( GRAB_HANDLE, - cursorPosition.x, - cursorPosition.y, - cursorInfo.lineHeight ); + FontId defaultFontId = 0u; + if( NULL == mFontDefaults ) + { + defaultFontId = mFontClient.GetFontId( EMPTY_STRING, + EMPTY_STRING ); + } + else + { + defaultFontId = mFontDefaults->GetFontId( mFontClient ); + } - if( cursorInfo.isSecondaryCursor ) - { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); - mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, - cursorInfo.secondaryPosition.x + offset.x, - cursorInfo.secondaryPosition.y + offset.y, - cursorInfo.secondaryCursorHeight, - cursorInfo.lineHeight ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); + Text::FontMetrics fontMetrics; + mFontClient.GetFontMetrics( defaultFontId, fontMetrics ); + + lineHeight = fontMetrics.ascender - fontMetrics.descender; + + + Vector2 cursorPosition; + + switch( mLayoutEngine.GetHorizontalAlignment() ) + { + case LayoutEngine::HORIZONTAL_ALIGN_BEGIN: + { + cursorPosition.x = 1.f; + break; + } + case LayoutEngine::HORIZONTAL_ALIGN_CENTER: + { + cursorPosition.x = floor( 0.5f * mControlSize.width ); + break; + } + case LayoutEngine::HORIZONTAL_ALIGN_END: + { + cursorPosition.x = mControlSize.width; + break; + } + } + + switch( mLayoutEngine.GetVerticalAlignment() ) + { + case LayoutEngine::VERTICAL_ALIGN_TOP: + { + cursorPosition.y = 0.f; + break; + } + case LayoutEngine::VERTICAL_ALIGN_CENTER: + { + cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) ); + break; + } + case LayoutEngine::VERTICAL_ALIGN_BOTTOM: + { + cursorPosition.y = mControlSize.height - lineHeight; + break; + } + } + + mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, + cursorPosition.x, + cursorPosition.y, + lineHeight, + lineHeight ); } else { - mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + CursorInfo cursorInfo; + GetCursorPosition( mEventData->mPrimaryCursorPosition, + cursorInfo ); + + const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; + const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; + + // Sets the cursor position. + mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, + cursorPosition.x, + cursorPosition.y, + cursorInfo.primaryCursorHeight, + cursorInfo.lineHeight ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y ); + + // Sets the grab handle position. + mEventData->mDecorator->SetPosition( GRAB_HANDLE, + cursorPosition.x, + cursorPosition.y, + cursorInfo.lineHeight ); + + if( cursorInfo.isSecondaryCursor ) + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); + mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, + cursorInfo.secondaryPosition.x + offset.x, + cursorInfo.secondaryPosition.y + offset.y, + cursorInfo.secondaryCursorHeight, + cursorInfo.lineHeight ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); + } + else + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + } } DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" ); } @@ -1138,27 +1519,19 @@ void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize ) } } -void Controller::Impl::ScrollToMakeCursorVisible() +void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position ) { - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - return; - } - - const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); - Vector2 offset; bool updateDecorator = false; - if( primaryCursorPosition.x < 0.f ) + if( position.x < 0.f ) { - offset.x = -primaryCursorPosition.x; + offset.x = -position.x; mEventData->mScrollPosition.x += offset.x; updateDecorator = true; } - else if( primaryCursorPosition.x > mControlSize.width ) + else if( position.x > mControlSize.width ) { - offset.x = mControlSize.width - primaryCursorPosition.x; + offset.x = mControlSize.width - position.x; mEventData->mScrollPosition.x += offset.x; updateDecorator = true; } @@ -1171,6 +1544,57 @@ void Controller::Impl::ScrollToMakeCursorVisible() // TODO : calculate the vertical scroll. } +void Controller::Impl::ScrollTextToMatchCursor() +{ + // Get the current cursor position in decorator coords. + const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR ); + + // Calculate the new cursor position. + CursorInfo cursorInfo; + GetCursorPosition( mEventData->mPrimaryCursorPosition, + cursorInfo ); + + // Calculate the offset to match the cursor position before the character was deleted. + mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x; + + ClampHorizontalScroll( mVisualModel->GetActualSize() ); + bool updateCursorPosition = true; + + const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; + const Vector2 cursorPosition = cursorInfo.primaryPosition + offset; + + if( updateCursorPosition ) + { + // Sets the cursor position. + mEventData->mDecorator->SetPosition( PRIMARY_CURSOR, + cursorPosition.x, + cursorPosition.y, + cursorInfo.primaryCursorHeight, + cursorInfo.lineHeight ); + + // Sets the grab handle position. + mEventData->mDecorator->SetPosition( GRAB_HANDLE, + cursorPosition.x, + cursorPosition.y, + cursorInfo.lineHeight ); + + if( cursorInfo.isSecondaryCursor ) + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH ); + mEventData->mDecorator->SetPosition( SECONDARY_CURSOR, + cursorInfo.secondaryPosition.x + offset.x, + cursorInfo.secondaryPosition.y + offset.y, + cursorInfo.secondaryCursorHeight, + cursorInfo.lineHeight ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y ); + } + else + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY ); + } + } +} + void Controller::Impl::RequestRelayout() { mControlInterface.RequestTextRelayout();