From: Paul Wisbey Date: Wed, 10 Jun 2015 14:24:11 +0000 (+0100) Subject: Selection implementation. X-Git-Tag: dali_1.0.46~20 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=e0c3ff90edd572ae3ba104d207deb1586eae4aed Selection implementation. Change-Id: I06668e73f1cf1fba9c2dbf100b23a3668511fae0 --- 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 6f868e6..305d61c 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -960,6 +960,24 @@ void TextField::OnRelayout( const Vector2& size, RelayoutContainer& container ) Self().Add( mRenderableActor ); } } + + for( std::vector::const_iterator it = mClippingDecorationActors.begin(), + endIt = mClippingDecorationActors.end(); + it != endIt; + ++it ) + { + Actor actor = *it; + + if( mClipper ) + { + mClipper->GetRootActor().Add( actor ); + } + else + { + Self().Add( actor ); + } + } + mClippingDecorationActors.clear(); } } @@ -1045,11 +1063,18 @@ bool TextField::OnKeyEvent( const KeyEvent& event ) return mController->KeyEvent( event ); } -void TextField::AddDecoration( Actor& actor ) +void TextField::AddDecoration( Actor& actor, bool needsClipping ) { if( actor ) { - Self().Add( actor ); + if( needsClipping ) + { + mClippingDecorationActors.push_back( actor ); + } + else + { + Self().Add( actor ); + } } } diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.h b/dali-toolkit/internal/controls/text-controls/text-field-impl.h index 0e53053..876972d 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.h @@ -144,7 +144,7 @@ private: // From Control /** * @copydoc Text::ControlInterface::AddDecoration() */ - virtual void AddDecoration( Actor& actor ); + virtual void AddDecoration( Actor& actor, bool needsClipping ); /** * @copydoc Text::ControlInterface::RequestTextRelayout() @@ -215,6 +215,7 @@ private: // Data Text::RendererPtr mRenderer; Text::DecoratorPtr mDecorator; Text::ClipperPtr mClipper; ///< For EXCEED_POLICY_CLIP + std::vector mClippingDecorationActors; ///< Decoration actors which need clipping. RenderableActor mRenderableActor; diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp index de713ac..77da8f9 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp @@ -503,7 +503,7 @@ void TextLabel::OnRelayout( const Vector2& size, RelayoutContainer& container ) } } -void TextLabel::AddDecoration( Actor& actor ) +void TextLabel::AddDecoration( Actor& actor, bool needsClipping ) { // TextLabel does not show decorations } diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.h b/dali-toolkit/internal/controls/text-controls/text-label-impl.h index 12dc56e..e45797a 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.h @@ -95,7 +95,7 @@ private: // From Control /** * @copydoc Text::ControlInterface::AddDecoration() */ - virtual void AddDecoration( Actor& actor ); + virtual void AddDecoration( Actor& actor, bool needsClipping ); /** * @copydoc Text::ControlInterface::RequestTextRelayout() diff --git a/dali-toolkit/internal/text/decorator/text-decorator.cpp b/dali-toolkit/internal/text/decorator/text-decorator.cpp index 2961c52..b2fcda0 100644 --- a/dali-toolkit/internal/text/decorator/text-decorator.cpp +++ b/dali-toolkit/internal/text/decorator/text-decorator.cpp @@ -81,7 +81,7 @@ const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-h const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f ); const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f ); -const Dali::Vector4 LIGHT_BLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // The text highlight color. +const Dali::Vector4 LIGHT_BLUE( (0xb2 / 255.0f), (0xeb / 255.0f), (0xf2 / 255.0f), 0.5f ); // The text highlight color. const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval const float TO_MILLISECONDS = 1000.f; @@ -224,7 +224,8 @@ struct Decorator::Impl : public ConnectionTracker mActiveCopyPastePopup( false ), mCursorBlinkStatus( true ), mPrimaryCursorVisible( false ), - mSecondaryCursorVisible( false ) + mSecondaryCursorVisible( false ), + mSwapSelectionHandles( false ) { } @@ -318,15 +319,65 @@ struct Decorator::Impl : public ConnectionTracker HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ]; if( primary.active || secondary.active ) { - SetupTouchEvents(); + Vector2 primaryPosition = primary.position; + Vector2 secondaryPosition = secondary.position; - CreateSelectionHandles(); + if( LEFT_SELECTION_HANDLE == mHandleScrolling ) + { + if( mScrollDirection == SCROLL_RIGHT ) + { + primaryPosition.x = 0.f; + } + else + { + primaryPosition.x = size.width; + } + } + else if( RIGHT_SELECTION_HANDLE == mHandleScrolling ) + { + if( mScrollDirection == SCROLL_RIGHT ) + { + secondaryPosition.x = 0.f; + } + else + { + secondaryPosition.x = size.width; + } + } + + const bool isPrimaryVisible = ( primaryPosition.x <= size.width ) && ( primaryPosition.x >= 0.f ); + const bool isSecondaryVisible = ( secondaryPosition.x <= size.width ) && ( secondaryPosition.x >= 0.f ); + + if( isPrimaryVisible || isSecondaryVisible ) + { + SetupTouchEvents(); + + CreateSelectionHandles(); + + if( isPrimaryVisible ) + { + primary.actor.SetPosition( primaryPosition.x, + primaryPosition.y + primary.lineHeight ); + + const bool flip = mSwapSelectionHandles ^ primary.flipped; + primary.actor.SetImage( flip ? mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] : mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); - primary.actor.SetPosition( primary.position.x, - primary.position.y + primary.lineHeight ); + primary.actor.SetAnchorPoint( flip ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT ); + } + + if( isSecondaryVisible ) + { + secondary.actor.SetPosition( secondaryPosition.x, + secondaryPosition.y + secondary.lineHeight ); - secondary.actor.SetPosition( secondary.position.x, - secondary.position.y + secondary.lineHeight ); + const bool flip = mSwapSelectionHandles ^ secondary.flipped; + + secondary.actor.SetImage( ( mSwapSelectionHandles ^ secondary.flipped ) ? mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] : mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); + secondary.actor.SetAnchorPoint( flip ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT ); + } + } + primary.actor.SetVisible( isPrimaryVisible ); + secondary.actor.SetVisible( isSecondaryVisible ); CreateHighlight(); UpdateHighlight(); @@ -367,8 +418,7 @@ struct Decorator::Impl : public ConnectionTracker mHandle[ GRAB_HANDLE ].position += scrollOffset; mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset; mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset; - - // TODO Highlight box?? + mHighlightPosition += scrollOffset; } void PopUpRelayoutComplete( Actor actor ) @@ -482,7 +532,8 @@ struct Decorator::Impl : public ConnectionTracker mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION ); - mController.AddDecoration( mActiveLayer ); + // Add the active layer telling the controller it doesn't need clipping. + mController.AddDecoration( mActiveLayer, false ); } mActiveLayer.RaiseToTop(); @@ -620,10 +671,12 @@ struct Decorator::Impl : public ConnectionTracker mHighlightMeshActor.SetName( "HighlightMeshActor" ); #endif mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); - mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET ); - mController.AddDecoration( mHighlightMeshActor ); + // Add the highlight box telling the controller it needs clipping. + mController.AddDecoration( mHighlightMeshActor, true ); } + + mHighlightMeshActor.SetPosition( mHighlightPosition.x, mHighlightPosition.y, DISPLAYED_HIGHLIGHT_Z_OFFSET ); } void UpdateHighlight() @@ -992,9 +1045,9 @@ struct Decorator::Impl : public ConnectionTracker if( HANDLE_TYPE_COUNT != mHandleScrolling ) { mController.DecorationEvent( mHandleScrolling, - HANDLE_SCROLLING, - mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance, - 0.f ); + HANDLE_SCROLLING, + mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance, + 0.f ); } return true; @@ -1022,6 +1075,7 @@ struct Decorator::Impl : public ConnectionTracker CursorImpl mCursor[CURSOR_COUNT]; HandleImpl mHandle[HANDLE_TYPE_COUNT]; QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight + Vector2 mHighlightPosition; ///< The position of the highlight actor. Rect mBoundingBox; Vector4 mHighlightColor; ///< Color of the highlight @@ -1040,6 +1094,7 @@ struct Decorator::Impl : public ConnectionTracker bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off. bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible. bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible. + bool mSwapSelectionHandles : 1; ///< Whether to swap the selection handle images. }; DecoratorPtr Decorator::New( ControllerInterface& controller ) @@ -1196,6 +1251,16 @@ void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& h height = handle.lineHeight; } +const Vector2& Decorator::GetPosition( HandleType handleType ) const +{ + return mImpl->mHandle[handleType].position; +} + +void Decorator::SwapSelectionHandlesEnabled( bool enable ) +{ + mImpl->mSwapSelectionHandles = enable; +} + void Decorator::AddHighlight( float x1, float y1, float x2, float y2 ) { mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) ); @@ -1204,6 +1269,7 @@ void Decorator::AddHighlight( float x1, float y1, float x2, float y2 ) void Decorator::ClearHighlights() { mImpl->mHighlightQuadList.clear(); + mImpl->mHighlightPosition = Vector2::ZERO; } void Decorator::SetHighlightColor( const Vector4& color ) diff --git a/dali-toolkit/internal/text/decorator/text-decorator.h b/dali-toolkit/internal/text/decorator/text-decorator.h index b271b7b..873bdcc 100644 --- a/dali-toolkit/internal/text/decorator/text-decorator.h +++ b/dali-toolkit/internal/text/decorator/text-decorator.h @@ -132,7 +132,7 @@ public: * * @param[in] decoration The actor displaying a decoration. */ - virtual void AddDecoration( Actor& actor ) = 0; + virtual void AddDecoration( Actor& actor, bool needsClipping ) = 0; /** * @brief An input event from one of the handles. @@ -355,6 +355,23 @@ public: void GetPosition( HandleType handleType, float& x, float& y, float& lineHeight ) const; /** + * @brief Retrieves the position of a selection handle. + * + * @param[in] handleType The handle to get. + * + * @return The position of the selection handle relative to the top-left of the parent control. + */ + const Vector2& GetPosition( HandleType handleType ) const; + + /** + * @brief Swaps the selection handle's images. + * + * This method is called by the text controller to swap the handles + * when the start index is bigger than the end one. + */ + void SwapSelectionHandlesEnabled( bool enable ); + + /** * @brief Adds a quad to the existing selection highlights. * * @param[in] x1 The top-left x position. diff --git a/dali-toolkit/internal/text/text-control-interface.h b/dali-toolkit/internal/text/text-control-interface.h index 4d74def..ede1531 100644 --- a/dali-toolkit/internal/text/text-control-interface.h +++ b/dali-toolkit/internal/text/text-control-interface.h @@ -50,8 +50,9 @@ public: * @brief Add a decoration. * * @param[in] decoration The actor displaying a decoration. + * @param[in] needsClipping Whether the actor needs clipping. */ - virtual void AddDecoration( Actor& actor ) = 0; + virtual void AddDecoration( Actor& actor, bool needsClipping ) = 0; /** * @brief Called to request a text relayout. diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 2d84c2d..f41bddd 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -129,13 +129,13 @@ EventData::EventData( DecoratorPtr decorator ) mCursorBlinkEnabled( true ), mGrabHandleEnabled( true ), mGrabHandlePopupEnabled( true ), - mSelectionEnabled( false ), + mSelectionEnabled( true ), mHorizontalScrollingEnabled( true ), mVerticalScrollingEnabled( false ), mUpdateCursorPosition( false ), mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), - mScrollAfterUpdateCursorPosition( false ), + mScrollAfterUpdatePosition( false ), mScrollAfterDelete( false ) {} @@ -193,10 +193,12 @@ 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; @@ -208,39 +210,56 @@ bool Controller::Impl::ProcessInputEvents() mEventData->mDecoratorUpdated = true; mEventData->mScrollAfterDelete = false; } - else if( mEventData->mUpdateLeftSelectionPosition ) + else { - UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); + bool leftScroll = false; + bool rightScroll = false; - if( mEventData->mScrollAfterUpdateCursorPosition ) + if( mEventData->mUpdateLeftSelectionPosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; - } + UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateLeftSelectionPosition = false; - } - else if( mEventData->mUpdateRightSelectionPosition ) - { - UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + if( mEventData->mScrollAfterUpdatePosition ) + { + const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE ); + + ScrollToMakePositionVisible( leftHandlePosition ); + leftScroll = true; + } - if( mEventData->mScrollAfterUpdateCursorPosition ) + mEventData->mDecoratorUpdated = true; + mEventData->mUpdateLeftSelectionPosition = false; + } + + if( mEventData->mUpdateRightSelectionPosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; + 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; } - 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; } @@ -438,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 ) @@ -463,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; } } } @@ -521,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 ) { @@ -547,6 +576,10 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( handleNewPosition != mEventData->mLeftSelectionPosition ) { mEventData->mLeftSelectionPosition = handleNewPosition; + + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + mEventData->mUpdateLeftSelectionPosition = true; } } @@ -557,36 +590,76 @@ void Controller::Impl::OnHandleEvent( const Event& event ) 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 ) { + CharacterIndex handlePosition = 0u; + 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; + + handlePosition = GetClosestCursorIndex( xPosition, yPosition ); + } + if( Event::GRAB_HANDLE_EVENT == event.type ) { mEventData->mUpdateCursorPosition = true; ChangeState( EventData::EDITING_WITH_POPUP ); - if( HANDLE_STOP_SCROLLING == state ) + 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->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition ); + mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition; + mEventData->mPrimaryCursorPosition = handlePosition; + } + } + else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::SELECTING ); - mEventData->mScrollAfterUpdateCursorPosition = true; + 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::LEFT_SELECTION_HANDLE_EVENT == event.type || Event::RIGHT_SELECTION_HANDLE_EVENT ) + else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { ChangeState( EventData::SELECTING ); + + 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; @@ -596,17 +669,105 @@ void Controller::Impl::OnHandleEvent( const Event& event ) ClampHorizontalScroll( actualSize ); + 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( Event::LEFT_SELECTION_HANDLE_EVENT == event.type || Event::RIGHT_SELECTION_HANDLE_EVENT ) + 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( CharacterIndex selectionStart, CharacterIndex selectionEnd ) +{ + mEventData->mDecorator->ClearHighlights(); + + mEventData->mLeftSelectionPosition = selectionStart; + mEventData->mRightSelectionPosition = selectionEnd; + + 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 float height = lines[0].ascender + -lines[0].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( 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; } void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY ) @@ -617,32 +778,35 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY return; } - // TODO - Find which word was selected - - const Vector& glyphs = mVisualModel->mGlyphs; - const Vector::SizeType glyphCount = glyphs.Count(); - - const Vector& positions = mVisualModel->mGlyphPositions; - const Vector::SizeType positionCount = positions.Count(); - - // Guard against glyphs which did not fit inside the layout - const Vector::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount; - - 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 ) @@ -777,6 +941,41 @@ LineIndex Controller::Impl::GetClosestLine( float y ) const 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 ) { @@ -847,9 +1046,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; @@ -863,7 +1063,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, @@ -1281,27 +1483,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; } diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 1a797b3..81125da 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -135,20 +135,20 @@ struct EventData 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. - bool mScrollAfterDelete : 1; ///< Whether to scroll after delete characters. + 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 mScrollAfterUpdatePosition : 1; ///< Whether to scroll after the cursor position is updated. + bool mScrollAfterDelete : 1; ///< Whether to scroll after delete characters. }; struct ModifyEvent @@ -323,12 +323,15 @@ struct Controller::Impl void OnHandleEvent( const Event& event ); + void RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd ); void RepositionSelectionHandles( float visualX, float visualY ); void ChangeState( EventData::State newState ); LineIndex GetClosestLine( float y ) const; + void FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex ); + /** * @brief Retrieves the cursor's logical position for a given touch point x,y * @@ -395,11 +398,16 @@ struct Controller::Impl void ClampVerticalScroll( const Vector2& actualSize ); /** - * @brief Scrolls the text to make the cursor visible. + * @brief Scrolls the text to make a position visible. + * + * @pre mEventData must not be NULL. (there is a text-input or selection capabilities). + * + * @param[in] position A position in decorator coords. * - * This method is called after inserting text or moving the cursor with the keypad. + * This method is called after inserting text, moving the cursor with the grab handle or the keypad, + * or moving the selection handles. */ - void ScrollToMakeCursorVisible(); + void ScrollToMakePositionVisible( const Vector2& position ); /** * @brief Scrolls the text to make the cursor visible. diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index 26d7ef0..3b36ed3 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -708,7 +708,7 @@ void Controller::ResetScrollPosition() { // Reset the scroll position. mImpl->mEventData->mScrollPosition = Vector2::ZERO; - mImpl->mEventData->mScrollAfterUpdateCursorPosition = true; + mImpl->mEventData->mScrollAfterUpdatePosition = true; } } @@ -749,7 +749,7 @@ void Controller::TextInsertedEvent() // Queue a cursor reposition event; this must wait until after DoRelayout() mImpl->mEventData->mUpdateCursorPosition = true; - mImpl->mEventData->mScrollAfterUpdateCursorPosition = true; + mImpl->mEventData->mScrollAfterUpdatePosition = true; } void Controller::TextDeletedEvent() @@ -1305,12 +1305,12 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y ) if( NULL != mImpl->mEventData ) { + const bool isShowingPlaceholderText = mImpl->IsShowingPlaceholderText(); if( 1u == tapCount ) { bool tapDuringEditMode( EventData::EDITING == mImpl->mEventData->mState ); - if( ! mImpl->IsShowingPlaceholderText() && - EventData::EDITING == mImpl->mEventData->mState ) + if( !isShowingPlaceholderText && tapDuringEditMode ) { // Grab handle is not shown until a tap is received whilst EDITING if( tapDuringEditMode ) @@ -1322,7 +1322,8 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y ) mImpl->ChangeState( EventData::EDITING ); } - else if( mImpl->mEventData->mSelectionEnabled && + else if( !isShowingPlaceholderText && + mImpl->mEventData->mSelectionEnabled && ( 2u == tapCount ) ) { mImpl->ChangeState( EventData::SELECTING ); @@ -1366,9 +1367,9 @@ void Controller::GetTargetSize( Vector2& targetSize ) targetSize = mImpl->mControlSize; } -void Controller::AddDecoration( Actor& actor ) +void Controller::AddDecoration( Actor& actor, bool needsClipping ) { - mImpl->mControlInterface.AddDecoration( actor ); + mImpl->mControlInterface.AddDecoration( actor, needsClipping ); } void Controller::DecorationEvent( HandleType handleType, HandleState state, float x, float y ) diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index 8428699..66bf3a0 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -532,7 +532,7 @@ public: /** * @copydoc Dali::Toolkit::Text::Decorator::ControllerInterface::AddDecoration() */ - virtual void AddDecoration( Actor& actor ); + virtual void AddDecoration( Actor& actor, bool needsClipping ); /** * @copydoc Dali::Toolkit::Text::Decorator::ControllerInterface::DecorationEvent()