From 54cc2865d9f0768b0e113a202e90dbf0864ff8b3 Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Tue, 28 Feb 2017 12:24:10 +0000 Subject: [PATCH 1/1] [3.0] Update double tap and long press behaviour. It updates the double tap and long press event on a text area with no text. i.e. On a position clearly after the last character of the text. * Double Tap : The cursor is placed at the beginning or at the end of the text. * Long Press : The cursor is placed at the beginning or at the end of the text and shows the text's selection popup. Change-Id: Iaf9ea817a515781d20c87f60991e1996d5440a62 Signed-off-by: Victor Cebollada --- .../controls/text-controls/text-field-impl.cpp | 3 + .../internal/text/cursor-helper-functions.cpp | 126 +++++++++++++------ .../internal/text/cursor-helper-functions.h | 34 +++++- .../internal/text/text-controller-impl.cpp | 133 ++++++++++++++++----- dali-toolkit/internal/text/text-controller-impl.h | 6 +- dali-toolkit/internal/text/text-controller.cpp | 94 +++++++++++---- dali-toolkit/internal/text/text-controller.h | 38 ++++++ 7 files changed, 343 insertions(+), 91 deletions(-) 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 63a934f..a69f9de 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -1149,6 +1149,9 @@ void TextField::OnInitialize() // Disable the smooth handle panning. mController->SetSmoothHandlePanEnabled( false ); + mController->SetNoTextDoubleTapAction( Controller::NoTextTap::NO_ACTION ); + mController->SetNoTextLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ); + // Forward input events to controller EnableGestureDetection( static_cast( Gesture::Tap | Gesture::Pan | Gesture::LongPress ) ); GetTapGestureDetector().SetMaximumTapsRequired( 2 ); diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index efeb837..50b18ed 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -43,7 +43,7 @@ struct FindWordData : textBuffer( textBuffer ), totalNumberOfCharacters( totalNumberOfCharacters ), hitCharacter( hitCharacter ), - foundIndex( 0u ), + foundIndex( 0 ), isWhiteSpace( isWhiteSpace ), isNewParagraph( isNewParagraph ) {} @@ -136,10 +136,17 @@ namespace Text { LineIndex GetClosestLine( VisualModelPtr visualModel, - float visualY ) + float visualY, + bool& matchedLine ) { float totalHeight = 0.f; - LineIndex lineIndex = 0u; + LineIndex lineIndex = 0; + matchedLine = false; + + if( visualY < 0.f ) + { + return 0; + } const Vector& lines = visualModel->mLines; @@ -156,16 +163,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel, if( visualY < totalHeight ) { + matchedLine = true; return lineIndex; } } - if( lineIndex == 0u ) + if( lineIndex == 0 ) { return 0; } - return lineIndex-1; + return lineIndex - 1u; } float CalculateLineOffset( const Vector& lines, @@ -192,11 +200,16 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, - float visualY ) + float visualY, + CharacterHitTest::Mode mode, + bool& matchedCharacter ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY ); - CharacterIndex logicalIndex = 0u; + // Whether there is a hit on a glyph. + matchedCharacter = false; + + CharacterIndex logicalIndex = 0; const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count(); const Length totalNumberOfLines = visualModel->mLines.Count(); @@ -206,9 +219,19 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, return logicalIndex; } + // Whether there is a hit on a line. + bool matchedLine = false; + // Find which line is closest. const LineIndex lineIndex = Text::GetClosestLine( visualModel, - visualY ); + visualY, + matchedLine ); + + if( !matchedLine && ( CharacterHitTest::TAP == mode ) ) + { + // Return the first or the last character if the touch point doesn't hit a line. + return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count(); + } // Convert from text's coords to line's coords. const LineRun& line = *( visualModel->mLines.Begin() + lineIndex ); @@ -241,12 +264,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // The character's direction buffer. const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL; - // Whether there is a hit on a glyph. - bool matched = false; + // Whether the touch point if before the first glyph. + bool isBeforeFirstGlyph = false; // Traverses glyphs in visual order. To do that use the visual to logical conversion table. CharacterIndex visualIndex = startCharacter; - Length numberOfVisualCharacters = 0u; + Length numberOfVisualCharacters = 0; for( ; visualIndex < endCharacter; ++visualIndex ) { // The character in logical order. @@ -257,7 +280,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); ++numberOfVisualCharacters; - if( 0u != numberOfGlyphs ) + if( 0 != numberOfGlyphs ) { // Get the first character/glyph of the group of glyphs. const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters; @@ -275,6 +298,17 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // Get the position of the first glyph. const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); + if( startCharacter == visualIndex ) + { + const float glyphPosition = -glyphMetrics.xBearing + position.x; + + if( visualX < glyphPosition ) + { + isBeforeFirstGlyph = true; + break; + } + } + // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا). Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex ); if( direction != LTR ) @@ -284,7 +318,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // number of glyphs in the table is found first. // Jump the number of characters to the next glyph is needed. - if( 0u == numberOfCharacters ) + if( 0 == numberOfCharacters ) { // TODO: This is a workaround to fix an issue with complex characters in the arabic // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة @@ -298,7 +332,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // Find the number of characters. for( GlyphIndex index = firstLogicalGlyphIndex + 1u; - ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ; + ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs ); ++index ) { numberOfCharacters = *( charactersPerGlyphBuffer + index ); @@ -322,7 +356,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u; const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfBlocks ); - CharacterIndex index = 0u; + CharacterIndex index = 0; for( ; index < numberOfBlocks; ++index ) { // Find the mid-point of the area containing the glyph @@ -330,31 +364,39 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, if( visualX < glyphCenter ) { - matched = true; + matchedCharacter = true; break; } } - if( matched ) + if( matchedCharacter ) { // If the glyph is shaped from more than one character, it matches the character of the glyph. visualIndex = firstVisualCharacterIndex + index; break; } - numberOfVisualCharacters = 0u; + numberOfVisualCharacters = 0; } - } + } // for characters in visual order. // The number of characters of the whole text. const Length totalNumberOfCharacters = logicalModel->mText.Count(); // Return the logical position of the cursor in characters. - if( !matched ) + if( !matchedCharacter ) { - // If no character is matched, then the last character (in visual order) of the line is used. - visualIndex = endCharacter; + if( isBeforeFirstGlyph ) + { + // If no character is matched, then the first character (in visual order) of the line is used. + visualIndex = startCharacter; + } + else + { + // If no character is matched, then the last character (in visual order) of the line is used. + visualIndex = endCharacter; + } } // Get the paragraph direction. @@ -607,7 +649,7 @@ void GetCursorPosition( GetCursorPositionParameters& parameters, isCurrentRightToLeft = *( directionsBuffer + index ); } - Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex; + Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex; if( isCurrentRightToLeft ) { numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; @@ -688,9 +730,9 @@ bool FindSelectionIndices( VisualModelPtr visualModel, float visualX, float visualY, CharacterIndex& startIndex, - CharacterIndex& endIndex ) + CharacterIndex& endIndex, + CharacterIndex& noTextHitIndex ) { - /* Hit character Select |-------------------------------------------------------|------------------------------------------| @@ -701,23 +743,33 @@ bool FindSelectionIndices( VisualModelPtr visualModel, | On a new paragraph character | The word or group of white spaces before | |-------------------------------------------------------|------------------------------------------| */ + const Length totalNumberOfCharacters = logicalModel->mText.Count(); + startIndex = 0; + endIndex = 0; + noTextHitIndex = 0; + if( 0 == totalNumberOfCharacters ) + { + // Nothing to do if the model is empty. + return false; + } + + bool matchedCharacter = false; CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel, logicalModel, metrics, visualX, - visualY ); - - const Length totalNumberOfCharacters = logicalModel->mText.Count(); - - DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + visualY, + CharacterHitTest::TAP, + matchedCharacter ); - if( 0u == totalNumberOfCharacters ) + if( !matchedCharacter ) { - // Nothing to do if the model is empty. - return false; + noTextHitIndex = hitCharacter; } + DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + if( hitCharacter >= totalNumberOfCharacters ) { // Closest hit character is the last character. @@ -753,7 +805,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel, { // Find the first character before the hit one which is not a new paragraph character. - if( hitCharacter > 0u ) + if( hitCharacter > 0 ) { endIndex = hitCharacter - 1u; for( ; endIndex > 0; --endIndex ) @@ -786,13 +838,13 @@ bool FindSelectionIndices( VisualModelPtr visualModel, { // Select the word before or after the white space - if( 0u == hitCharacter ) + if( 0 == hitCharacter ) { data.isWhiteSpace = false; FindEndOfWord( data ); endIndex = data.foundIndex; } - else if( hitCharacter > 0u ) + else if( hitCharacter > 0 ) { // Find the start of the word. data.hitCharacter = hitCharacter - 1u; @@ -805,7 +857,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel, } } - return true; + return matchedCharacter; } } // namespace Text diff --git a/dali-toolkit/internal/text/cursor-helper-functions.h b/dali-toolkit/internal/text/cursor-helper-functions.h index 1578494..89031e0 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.h +++ b/dali-toolkit/internal/text/cursor-helper-functions.h @@ -32,6 +32,18 @@ namespace Toolkit namespace Text { +struct CharacterHitTest +{ + /** + * @brief Enumeration of the types of hit test. + */ + enum Mode + { + TAP, ///< Retrieves the first or last character of the line if the touch point is outside of the boundaries of the text. + SCROLL ///< Retrieves the character above or below to the touch point if it's outside of the boundaries of the text. + }; +}; + struct CursorInfo { CursorInfo() @@ -75,11 +87,13 @@ struct GetCursorPositionParameters * * @param[in] visualModel The visual model. * @param[in] visualY The touch point 'y' in text's coords. + * @param[out] matchedLine Whether the touch point actually hits a line. * * @return A line index. */ LineIndex GetClosestLine( VisualModelPtr visualModel, - float visualY ); + float visualY, + bool& matchedLine ); /** * @brief Calculates the vertical line's offset for a given line. @@ -97,11 +111,18 @@ float CalculateLineOffset( const Vector& lines, /** * @brief Retrieves the cursor's logical position for a given touch point x,y * + * There are two types of hit test: CharacterHitTest::TAP retrieves the first or + * last character of a line if the touch point is outside the boundaries of the + * text, CharacterHitTest::SCROLL retrieves the character above or below to the + * touch point if it's outside the boundaries of the text. + * * @param[in] visualModel The visual model. * @param[in] logicalModel The logical model. * @param[in] metrics A wrapper around FontClient used to get metrics. * @param[in] visualX The touch point 'x' in text's coords. * @param[in] visualY The touch point 'y' in text's coords. + * @param[in] mode The type of hit test. + * @param[out] matchedCharacter Whether the touch point actually hits a character. * * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. */ @@ -109,8 +130,9 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, - float visualY ); - + float visualY, + CharacterHitTest::Mode mode, + bool& matchedCharacter ); /** * @brief Calculates the cursor's position for a given character index in the logical order. @@ -134,8 +156,9 @@ void GetCursorPosition( GetCursorPositionParameters& parameters, * @param[in] visualY The touch point 'y' in text's coords. * @param[out] startIndex Index to the first character of the selected word. * @param[out] endIndex Index to the last character of the selected word. + * @param[out] noTextHitIndex Index to the nearest character when there is no hit. * - * @return @e true if the indices are found. + * @return @e true if the touch point hits a character. */ bool FindSelectionIndices( VisualModelPtr visualModel, LogicalModelPtr logicalModel, @@ -143,7 +166,8 @@ bool FindSelectionIndices( VisualModelPtr visualModel, float visualX, float visualY, CharacterIndex& startIndex, - CharacterIndex& endIndex ); + CharacterIndex& endIndex, + CharacterIndex& noTextHitIndex ); } // namespace Text diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 2f7dd46..b3e9147 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -83,6 +83,8 @@ EventData::EventData( DecoratorPtr decorator ) mPreEditStartPosition( 0u ), mPreEditLength( 0u ), mCursorHookPositionX( 0.f ), + mDoubleTapAction( Controller::NoTextTap::NO_ACTION ), + mLongPressAction( Controller::NoTextTap::SHOW_SELECTION_POPUP ), mIsShowingPlaceholderText( false ), mPreEditFlag( false ), mDecoratorUpdated( false ), @@ -96,6 +98,7 @@ EventData::EventData( DecoratorPtr decorator ) mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), mIsLeftHandleSelected( false ), + mIsRightHandleSelected( false ), mUpdateHighlightBox( false ), mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ), @@ -225,10 +228,25 @@ bool Controller::Impl::ProcessInputEvents() if( mEventData->mScrollAfterUpdatePosition && ( mEventData->mIsLeftHandleSelected ? mEventData->mUpdateLeftSelectionPosition : mEventData->mUpdateRightSelectionPosition ) ) { - CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; + if( mEventData->mIsLeftHandleSelected && mEventData->mIsRightHandleSelected ) + { + CursorInfo& infoLeft = leftHandleInfo; + + const Vector2 currentCursorPositionLeft( infoLeft.primaryPosition.x, infoLeft.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionLeft, infoLeft.lineHeight ); + + CursorInfo& infoRight = rightHandleInfo; + + const Vector2 currentCursorPositionRight( infoRight.primaryPosition.x, infoRight.lineOffset ); + ScrollToMakePositionVisible( currentCursorPositionRight, infoRight.lineHeight ); + } + else + { + CursorInfo& info = mEventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo; - const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); - ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + const Vector2 currentCursorPosition( info.primaryPosition.x, info.lineOffset ); + ScrollToMakePositionVisible( currentCursorPosition, info.lineHeight ); + } } } @@ -259,6 +277,8 @@ bool Controller::Impl::ProcessInputEvents() mEventData->mUpdateLeftSelectionPosition = false; mEventData->mUpdateRightSelectionPosition = false; mEventData->mUpdateHighlightBox = false; + mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = false; } mEventData->mScrollAfterUpdatePosition = false; @@ -1148,14 +1168,17 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); } } - else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) + else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode ) { // Get first the line index of the current cursor position index. CharacterIndex characterIndex = 0u; @@ -1181,11 +1204,14 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender ); // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, mEventData->mCursorHookPositionX, - hitPointY ); + hitPointY, + CharacterHitTest::TAP, + matchedCharacter ); } } @@ -1211,11 +1237,15 @@ void Controller::Impl::OnTapEvent( const Event& event ) // Keep the tap 'x' position. Used to move the cursor. mEventData->mCursorHookPositionX = xPosition; + // Whether to touch point hits on a glyph. + bool matchedCharacter = false; mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::TAP, + matchedCharacter ); // When the cursor position is changing, delay cursor blinking mEventData->mDecorator->DelayCursorBlink(); @@ -1237,6 +1267,20 @@ void Controller::Impl::OnTapEvent( const Event& event ) mEventData->mImfManager.NotifyCursorPosition(); } } + else if( 2u == tapCount ) + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mScrollPosition.x; + const float yPosition = event.p3.mFloat - mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mDoubleTapAction ); + } + } } } @@ -1307,12 +1351,26 @@ void Controller::Impl::OnLongPressEvent( const Event& event ) { DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" ); - if( EventData::EDITING == mEventData->mState ) + if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) ) { - ChangeState ( EventData::EDITING_WITH_POPUP ); + ChangeState( EventData::EDITING_WITH_POPUP ); mEventData->mDecoratorUpdated = true; mEventData->mUpdateInputStyle = true; } + else + { + if( mEventData->mSelectionEnabled ) + { + // Convert from control's coords to text's coords. + const float xPosition = event.p2.mFloat - mScrollPosition.x; + const float yPosition = event.p3.mFloat - mScrollPosition.y; + + // Calculates the logical position from the x,y coords. + RepositionSelectionHandles( xPosition, + yPosition, + mEventData->mLongPressAction ); + } + } } void Controller::Impl::OnHandleEvent( const Event& event ) @@ -1334,11 +1392,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float yPosition = event.p3.mFloat - mScrollPosition.y; // Need to calculate the handle's new position. + bool matchedCharacter = false; const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); if( Event::GRAB_HANDLE_EVENT == event.type ) { @@ -1375,6 +1436,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = true; + mEventData->mIsRightHandleSelected = false; } else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { @@ -1395,6 +1457,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Will define the order to scroll the text to match the handle position. mEventData->mIsLeftHandleSelected = false; + mEventData->mIsRightHandleSelected = true; } } // end ( HANDLE_PRESSED == state ) else if( ( HANDLE_RELEASED == state ) || @@ -1407,11 +1470,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float xPosition = event.p2.mFloat - mScrollPosition.x; const float yPosition = event.p3.mFloat - mScrollPosition.y; + bool matchedCharacter = false; handlePosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, xPosition, - yPosition ); + yPosition, + CharacterHitTest::SCROLL, + matchedCharacter ); } if( Event::GRAB_HANDLE_EVENT == event.type ) @@ -1523,11 +1589,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The grab handle's position is in decorator's coords. Need to transforms to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, position.x - mScrollPosition.x, - position.y - mScrollPosition.y ); + position.y - mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( mEventData->mPrimaryCursorPosition != handlePosition ) { @@ -1564,11 +1633,14 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The selection handle's position is in decorator's coords. Need to transform to text's coords. + bool matchedCharacter = false; const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel, mLogicalModel, mMetrics, position.x - mScrollPosition.x, - position.y - mScrollPosition.y ); + position.y - mScrollPosition.y, + CharacterHitTest::SCROLL, + matchedCharacter ); if( leftSelectionHandleEvent ) { @@ -1621,7 +1693,8 @@ void Controller::Impl::OnSelectEvent( const Event& event ) // Calculates the logical position from the x,y coords. RepositionSelectionHandles( xPosition, - yPosition ); + yPosition, + Controller::NoTextTap::HIGHLIGHT ); } } @@ -2162,7 +2235,7 @@ void Controller::Impl::RepositionSelectionHandles() mEventData->mDecoratorUpdated = true; } -void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) +void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ) { if( NULL == mEventData ) { @@ -2170,12 +2243,6 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY return; } - if( IsShowingPlaceholderText() ) - { - // Nothing to do if there is the place-holder text. - return; - } - const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); const Length numberOfLines = mVisualModel->mLines.Count(); if( ( 0 == numberOfGlyphs ) || @@ -2188,16 +2255,18 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY // Find which word was selected CharacterIndex selectionStart( 0 ); CharacterIndex selectionEnd( 0 ); - const bool indicesFound = FindSelectionIndices( mVisualModel, + CharacterIndex noTextHitIndex( 0 ); + const bool characterHit = FindSelectionIndices( mVisualModel, mLogicalModel, mMetrics, visualX, visualY, selectionStart, - selectionEnd ); + selectionEnd, + noTextHitIndex ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - if( indicesFound ) + if( characterHit || ( Controller::NoTextTap::HIGHLIGHT == action ) ) { ChangeState( EventData::SELECTING ); @@ -2216,12 +2285,22 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); } - else + else if( Controller::NoTextTap::SHOW_SELECTION_POPUP == action ) { // Nothing to select. i.e. a white space, out of bounds - ChangeState( EventData::EDITING ); + ChangeState( EventData::EDITING_WITH_POPUP ); + + mEventData->mPrimaryCursorPosition = noTextHitIndex; - mEventData->mPrimaryCursorPosition = selectionEnd; + mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateInputStyle = true; + } + else if( Controller::NoTextTap::NO_ACTION == action ) + { + // Nothing to select. i.e. a white space, out of bounds + mEventData->mPrimaryCursorPosition = noTextHitIndex; mEventData->mUpdateCursorPosition = true; mEventData->mUpdateGrabHandlePosition = true; diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 44ae88e..5285b19 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -132,6 +132,9 @@ struct EventData float mCursorHookPositionX; ///< Used to move the cursor with the keys or when scrolling the text vertically with the handles. + Controller::NoTextTap::Action mDoubleTapAction; ///< Action to be done when there is a double tap on top of 'no text' + Controller::NoTextTap::Action mLongPressAction; ///< Action to be done when there is a long press on top of 'no text' + 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. @@ -145,6 +148,7 @@ struct EventData 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 mIsLeftHandleSelected : 1; ///< Whether is the left handle the one which is selected. + bool mIsRightHandleSelected : 1; ///< Whether is the right handle the one which is selected. bool mUpdateHighlightBox : 1; ///< True if the text selection high light box must be updated. bool mScrollAfterUpdatePosition : 1; ///< Whether to scroll after the cursor position is updated. bool mScrollAfterDelete : 1; ///< Whether to scroll after delete characters. @@ -597,7 +601,7 @@ struct Controller::Impl void RequestGetTextFromClipboard(); void RepositionSelectionHandles(); - void RepositionSelectionHandles( float visualX, float visualY ); + void RepositionSelectionHandles( float visualX, float visualY, Controller::NoTextTap::Action action ); void SetPopupButtons(); diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index b52e739..ed5b847 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -1335,6 +1335,46 @@ const std::string& Controller::GetInputOutlineProperties() const return GetDefaultOutlineProperties(); } +void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action ) +{ + if( NULL != mImpl->mEventData ) + { + mImpl->mEventData->mDoubleTapAction = action; + } +} + +Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const +{ + NoTextTap::Action action = NoTextTap::NO_ACTION; + + if( NULL != mImpl->mEventData ) + { + action = mImpl->mEventData->mDoubleTapAction; + } + + return action; +} + +void Controller::SetNoTextLongPressAction( NoTextTap::Action action ) +{ + if( NULL != mImpl->mEventData ) + { + mImpl->mEventData->mLongPressAction = action; + } +} + +Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const +{ + NoTextTap::Action action = NoTextTap::NO_ACTION; + + if( NULL != mImpl->mEventData ) + { + return mImpl->mEventData->mLongPressAction; + } + + return action; +} + // public : Queries & retrieves. LayoutEngine& Controller::GetLayoutEngine() @@ -1809,9 +1849,12 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y ) if( mImpl->mEventData->mSelectionEnabled && mImpl->IsShowingRealText() ) { - SelectEvent( x, y, false ); + relayoutNeeded = true; + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; } } + // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated if( relayoutNeeded ) { @@ -1852,35 +1895,42 @@ void Controller::LongPressEvent( Gesture::State state, float x, float y ) if( ( state == Gesture::Started ) && ( NULL != mImpl->mEventData ) ) { - if( !mImpl->IsShowingRealText() ) + // The 1st long-press on inactive text-field is treated as tap + if( EventData::INACTIVE == mImpl->mEventData->mState ) + { + mImpl->ChangeState( EventData::EDITING ); + + Event event( Event::TAP_EVENT ); + event.p1.mUint = 1; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mEventData->mEventQueue.push_back( event ); + + mImpl->RequestRelayout(); + } + else if( !mImpl->IsShowingRealText() ) { Event event( Event::LONG_PRESS_EVENT ); event.p1.mInt = state; + event.p2.mFloat = x; + event.p3.mFloat = y; mImpl->mEventData->mEventQueue.push_back( event ); mImpl->RequestRelayout(); } - else + else if( !mImpl->IsClipboardVisible() ) { - // The 1st long-press on inactive text-field is treated as tap - if( EventData::INACTIVE == mImpl->mEventData->mState ) - { - mImpl->ChangeState( EventData::EDITING ); + // Reset the imf manager to commit the pre-edit before selecting the text. + mImpl->ResetImfManager(); - Event event( Event::TAP_EVENT ); - event.p1.mUint = 1; - event.p2.mFloat = x; - event.p3.mFloat = y; - mImpl->mEventData->mEventQueue.push_back( event ); - - mImpl->RequestRelayout(); - } - else - { - // Reset the imf manger to commit the pre-edit before selecting the text. - mImpl->ResetImfManager(); + Event event( Event::LONG_PRESS_EVENT ); + event.p1.mInt = state; + event.p2.mFloat = x; + event.p3.mFloat = y; + mImpl->mEventData->mEventQueue.push_back( event ); + mImpl->RequestRelayout(); - SelectEvent( x, y, false ); - } + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; } } } @@ -2860,6 +2910,8 @@ void Controller::SelectEvent( float x, float y, bool selectAll ) mImpl->mEventData->mEventQueue.push_back( event ); } + mImpl->mEventData->mIsLeftHandleSelected = true; + mImpl->mEventData->mIsRightHandleSelected = true; mImpl->RequestRelayout(); } } diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index f99d5a9..71d0695 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -118,6 +118,16 @@ public: // Enumerated types. PLACEHOLDER_TYPE_INACTIVE, }; + struct NoTextTap + { + enum Action + { + NO_ACTION, ///< Does no action if there is a tap on top of an area with no text. + HIGHLIGHT, ///< Highlights the nearest text (at the beginning or end of the text) and shows the text's selection popup. + SHOW_SELECTION_POPUP ///< Shows the text's selection popup. + }; + }; + public: // Constructor. /** @@ -316,6 +326,34 @@ public: // Configure the text controller. */ LayoutEngine::VerticalAlignment GetVerticalAlignment() const; + /** + * @brief Sets the action when there is a double tap event on top of a text area with no text. + * + * @param[in] action The action to do. + */ + void SetNoTextDoubleTapAction( NoTextTap::Action action ); + + /** + * @brief Retrieves the action when there is a double tap event on top of a text area with no text. + * + * @return The action to do. + */ + NoTextTap::Action GetNoTextDoubleTapAction() const; + + /** + * @briefSets the action when there is a long press event on top of a text area with no text. + * + * @param[in] action The action to do. + */ + void SetNoTextLongPressAction( NoTextTap::Action action ); + + /** + * @brief Retrieves the action when there is a long press event on top of a text area with no text. + * + * @return The action to do. + */ + NoTextTap::Action GetNoTextLongPressAction() const; + public: // Update. /** -- 2.7.4