X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Fdecorator%2Ftext-decorator.cpp;h=5f19660b6d0011e7e2345c7c845a60fef48f8f72;hp=92b475cef3eb0689631b52d4fba22a687f1a3e4c;hb=a2de9cf491172cd5da9dc9ed60b17683dab6d7bc;hpb=ebbf2aed6e030f66f640d0f4c933e76e9b63ee27 diff --git a/dali-toolkit/internal/text/decorator/text-decorator.cpp b/dali-toolkit/internal/text/decorator/text-decorator.cpp index 92b475c..bb393ff 100644 --- a/dali-toolkit/internal/text/decorator/text-decorator.cpp +++ b/dali-toolkit/internal/text/decorator/text-decorator.cpp @@ -19,53 +19,91 @@ #include // EXTERNAL INCLUDES -#include +#include + #include -#include #include -#include -#include -#include -#include +#include +#include #include -#include -#include -#include #include -#include -#include -//#include -#include +#include + +#include +#include // INTERNAL INCLUDES -#include -#include -#include -#include -#include -#include +#include +#include +#include #ifdef DEBUG_ENABLED #define DECORATOR_DEBUG + +#endif + +#define MAKE_SHADER(A)#A + +namespace +{ +const char* VERTEX_SHADER = MAKE_SHADER( +attribute mediump vec2 aPosition; +uniform mediump mat4 uMvpMatrix; +uniform mediump vec3 uSize; + +void main() +{ + mediump vec4 position = vec4( aPosition, 0.0, 1.0 ); + position.xyz *= uSize; + gl_Position = uMvpMatrix * position; +} +); + +const char* FRAGMENT_SHADER = MAKE_SHADER( +uniform lowp vec4 uColor; + +void main() +{ + gl_FragColor = uColor; +} +); +} + +namespace Dali +{ +namespace Internal +{ +namespace +{ +#ifdef DECORATOR_DEBUG +Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") ); #endif +} +} +} + // Local Data namespace { +const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f ); +const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f ); + +const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight color. TODO: due some problems, maybe with the blending function in the text clipping, the color is fully opaque. -const char* DEFAULT_GRAB_HANDLE_IMAGE( DALI_IMAGE_DIR "insertpoint-icon.png" ); -const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" ); -const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" ); -//const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" ); -//const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" ); +const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f ); -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 unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval +const float TO_MILLISECONDS = 1000.f; +const float TO_SECONDS = 1.f / TO_MILLISECONDS; -const std::size_t CURSOR_BLINK_INTERVAL = 500; // Cursor blink interval -const std::size_t MILLISECONDS = 1000; +const unsigned int SCROLL_TICK_INTERVAL = 50u; -const float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f ); +const float SCROLL_THRESHOLD = 10.f; +const float SCROLL_SPEED = 300.f; +const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS; + +const float CURSOR_WIDTH = 1.f; /** * structure to hold coordinates of each quad, which will make up the mesh. @@ -98,6 +136,36 @@ struct QuadCoordinates typedef std::vector QuadContainer; +/** + * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box. + * @param[in] boundingRectangle local bounding + * @param[out] Vector4 World coordinate bounding Box. + */ +void LocalToWorldCoordinatesBoundingBox( const Dali::Rect& boundingRectangle, Dali::Vector4& boundingBox ) +{ + // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications. + Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize(); + + const float originX = boundingRectangle.x - 0.5f * stageSize.width; + const float originY = boundingRectangle.y - 0.5f * stageSize.height; + + boundingBox = Dali::Vector4( originX, + originY, + originX + boundingRectangle.width, + originY + boundingRectangle.height ); +} + +void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect& boundingRectangle ) +{ + // Convert to local coordinates and store as a Dali::Rect. + Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize(); + + boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width; + boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height; + boundingRectangle.width = boundingBox.z - boundingBox.x; + boundingRectangle.height = boundingBox.w - boundingBox.y; +} + } // end of namespace namespace Dali @@ -111,149 +179,356 @@ namespace Text struct Decorator::Impl : public ConnectionTracker { + enum ScrollDirection + { + SCROLL_NONE, + SCROLL_RIGHT, + SCROLL_LEFT, + SCROLL_TOP, + SCROLL_BOTTOM + }; + struct CursorImpl { CursorImpl() - : x(0.0f), - y(0.0f), - height(0.0f), - color(Dali::Color::WHITE) + : color( Dali::Color::BLACK ), + position(), + cursorHeight( 0.0f ), + lineHeight( 0.0f ) { } - float x; - float y; - float height; - Vector4 color; + Vector2 position; + float cursorHeight; + float lineHeight; }; - struct SelectionHandleImpl + struct HandleImpl { - SelectionHandleImpl() - : x(0.0f), - y(0.0f), - cursorHeight(0.0f), - flipped(false) + HandleImpl() + : position(), + size(), + lineHeight( 0.0f ), + grabDisplacementX( 0.f ), + grabDisplacementY( 0.f ), + active( false ), + visible( false ), + pressed( false ), + verticallyFlippedPreferred( false ), + horizontallyFlipped( false ), + verticallyFlipped( false ) { } - float x; - float y; - float cursorHeight; ///< Not the handle height - bool flipped; - - ImageActor actor; + ImageView actor; Actor grabArea; + ImageView markerActor; + + Vector2 position; + Size size; + float lineHeight; ///< Not the handle height + float grabDisplacementX; + float grabDisplacementY; + bool active : 1; + bool visible : 1; + bool pressed : 1; + bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped. + bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped. + bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped. + }; + + struct PopupImpl + { + PopupImpl() + : position() + { + } - Image pressedImage; - Image releasedImage; + TextSelectionPopup actor; + Vector3 position; }; - Impl( Dali::Toolkit::Internal::Control& parent, Observer& observer ) - : mTextControlParent(parent), - mObserver(observer), - mActiveCursor(ACTIVE_CURSOR_NONE), - mActiveGrabHandle(false), - mActiveSelection( false ), - mActiveCopyPastePopup( false ), + Impl( ControllerInterface& controller, + TextSelectionPopupCallbackInterface& callbackInterface ) + : mController( controller ), + mEnabledPopupButtons( TextSelectionPopup::NONE ), + mTextSelectionPopupCallbackInterface( callbackInterface ), + mHandleColor( HANDLE_COLOR ), + mBoundingBox(), + mHighlightColor( LIGHT_BLUE ), + mHighlightPosition( Vector2::ZERO ), + mActiveCursor( ACTIVE_CURSOR_NONE ), mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ), mCursorBlinkDuration( 0.0f ), + mCursorWidth( CURSOR_WIDTH ), + mHandleScrolling( HANDLE_TYPE_COUNT ), + mScrollDirection( SCROLL_NONE ), + mScrollThreshold( SCROLL_THRESHOLD ), + mScrollSpeed( SCROLL_SPEED ), + mScrollDistance( SCROLL_DISTANCE ), + mTextDepth( 0u ), + mActiveCopyPastePopup( false ), + mPopupSetNewPosition( true ), mCursorBlinkStatus( true ), - mGrabDisplacementX( 0.0f ), - mGrabDisplacementY( 0.0f ), - mHighlightColor( 0.07f, 0.41f, 0.59f, 1.0f ), // light blue - mBoundingBox( Rect() ) + mDelayCursorBlink( false ), + mPrimaryCursorVisible( false ), + mSecondaryCursorVisible( false ), + mFlipSelectionHandlesOnCross( false ), + mFlipLeftSelectionHandleDirection( false ), + mFlipRightSelectionHandleDirection( false ), + mHandlePanning( false ), + mHandleCurrentCrossed( false ), + mHandlePreviousCrossed( false ), + mNotifyEndOfScroll( false ) { + mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2; + mQuadIndexFormat[ "indices" ] = Property::INTEGER; + mHighlightShader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ); + SetupTouchEvents(); } /** * Relayout of the decorations owned by the decorator. - * @param[in] size The Size of the UI control the decorater is adding it's decorations to. + * @param[in] size The Size of the UI control the decorator is adding it's decorations to. */ - void Relayout( const Vector2& size, const Vector2& scrollPosition ) + void Relayout( const Vector2& size ) { + mControlSize = size; + // TODO - Remove this if nothing is active CreateActiveLayer(); // Show or hide the cursors CreateCursors(); + if( mPrimaryCursor ) { - mPrimaryCursor.SetPosition( mCursor[PRIMARY_CURSOR].x + scrollPosition.x, - mCursor[PRIMARY_CURSOR].y + scrollPosition.y ); - mPrimaryCursor.SetSize( 1.0f, mCursor[PRIMARY_CURSOR].height ); + const CursorImpl& cursor = mCursor[PRIMARY_CURSOR]; + mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f ); + if( mPrimaryCursorVisible ) + { + mPrimaryCursor.SetPosition( cursor.position.x, + cursor.position.y ); + mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) ); + } + mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus ); } if( mSecondaryCursor ) { - mSecondaryCursor.SetPosition( mCursor[SECONDARY_CURSOR].x + scrollPosition.x, - mCursor[SECONDARY_CURSOR].y + scrollPosition.y ); - mSecondaryCursor.SetSize( 1.0f, mCursor[SECONDARY_CURSOR].height ); + const CursorImpl& cursor = mCursor[SECONDARY_CURSOR]; + mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f ); + if( mSecondaryCursorVisible ) + { + mSecondaryCursor.SetPosition( cursor.position.x, + cursor.position.y ); + mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) ); + } + mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus ); } // Show or hide the grab handle - if( mActiveGrabHandle ) + HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + bool newGrabHandlePosition = false; + if( grabHandle.active ) { - SetupTouchEvents(); + const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f ); + + if( isVisible ) + { + CreateGrabHandle(); - CreateGrabHandle(); + // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box. + SetGrabHandlePosition(); - mGrabHandle.SetPosition( mCursor[PRIMARY_CURSOR].x + scrollPosition.x, - mCursor[PRIMARY_CURSOR].y + scrollPosition.y + mCursor[PRIMARY_CURSOR].height ); + // Sets the grab handle image according if it's pressed, flipped, etc. + SetHandleImage( GRAB_HANDLE ); + + newGrabHandlePosition = true; + } + + if( grabHandle.actor ) + { + grabHandle.actor.SetVisible( isVisible ); + } } - else if( mGrabHandle ) + else if( grabHandle.actor ) { - UnparentAndReset( mGrabHandle ); + grabHandle.actor.Unparent(); } // Show or hide the selection handles/highlight - if( mActiveSelection ) + HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ]; + HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ]; + bool newPrimaryHandlePosition = false; + bool newSecondaryHandlePosition = false; + if( primary.active || secondary.active ) { - SetupTouchEvents(); + const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f ); + const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f ); + + if( isPrimaryVisible || isSecondaryVisible ) + { + CreateSelectionHandles(); + + if( isPrimaryVisible ) + { + SetSelectionHandlePosition( LEFT_SELECTION_HANDLE ); + + // Sets the primary handle image according if it's pressed, flipped, etc. + SetHandleImage( LEFT_SELECTION_HANDLE ); + + SetSelectionHandleMarkerSize( primary ); + + newPrimaryHandlePosition = true; + } + + if( isSecondaryVisible ) + { + SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE ); + + // Sets the secondary handle image according if it's pressed, flipped, etc. + SetHandleImage( RIGHT_SELECTION_HANDLE ); - CreateSelectionHandles(); + SetSelectionHandleMarkerSize( secondary ); - SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ]; - primary.actor.SetPosition( primary.x + scrollPosition.x, - primary.y + scrollPosition.y + primary.cursorHeight ); + newSecondaryHandlePosition = true; + } + } - SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ]; - secondary.actor.SetPosition( secondary.x + scrollPosition.x, - secondary.y + scrollPosition.y + secondary.cursorHeight ); + if( primary.actor ) + { + primary.actor.SetVisible( isPrimaryVisible ); + } + if( secondary.actor ) + { + secondary.actor.SetVisible( isSecondaryVisible ); + } CreateHighlight(); UpdateHighlight(); } else { - UnparentAndReset( mSelectionHandle[ PRIMARY_SELECTION_HANDLE ].actor ); - UnparentAndReset( mSelectionHandle[ SECONDARY_SELECTION_HANDLE ].actor ); - UnparentAndReset( mHighlightMeshActor ); + if( primary.actor ) + { + primary.actor.Unparent(); + } + if( secondary.actor ) + { + secondary.actor.Unparent(); + } + if( mHighlightActor ) + { + mHighlightActor.Unparent(); + } + } + + if( newGrabHandlePosition || + newPrimaryHandlePosition || + newSecondaryHandlePosition ) + { + // Setup property notifications to find whether the handles leave the boundaries of the current display. + SetupActiveLayerPropertyNotifications(); } - if ( mActiveCopyPastePopup ) + if( mActiveCopyPastePopup ) + { + ShowPopup(); + mPopupSetNewPosition = true; + } + else { - if ( !mCopyPastePopup ) + if( mCopyPastePopup.actor ) { - mCopyPastePopup = TextSelectionPopup::New(); - mActiveLayer.Add ( mCopyPastePopup ); + mCopyPastePopup.actor.HidePopup(); + mPopupSetNewPosition = true; } - mCopyPastePopup.SetPosition( Vector3( 200.0f, -100.0f, 0.0f ) ); //todo grabhandle or selection handle positions to be used } - else + } + + void UpdatePositions( const Vector2& scrollOffset ) + { + mCursor[PRIMARY_CURSOR].position += scrollOffset; + mCursor[SECONDARY_CURSOR].position += scrollOffset; + mHandle[ GRAB_HANDLE ].position += scrollOffset; + mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset; + mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset; + mHighlightPosition += scrollOffset; + } + + void ShowPopup() + { + if ( !mCopyPastePopup.actor ) + { + return; + } + + if( !mCopyPastePopup.actor.GetParent() ) + { + mActiveLayer.Add( mCopyPastePopup.actor ); + } + + mCopyPastePopup.actor.RaiseAbove( mActiveLayer ); + mCopyPastePopup.actor.ShowPopup(); + } + + void DeterminePositionPopup() + { + if( !mActiveCopyPastePopup ) + { + return; + } + + // Retrieves the popup's size after relayout. + const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f ); + + if( mPopupSetNewPosition ) { - if ( mCopyPastePopup ) - { - UnparentAndReset( mCopyPastePopup ); - } + const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE]; + const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE]; + const HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + const CursorImpl& cursor = mCursor[PRIMARY_CURSOR]; + + if( primaryHandle.active || secondaryHandle.active ) + { + // Calculates the popup's position if selection handles are active. + const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x ); + const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x ); + const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height ); + + mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f ); + mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y ); + } + else + { + // Calculates the popup's position if the grab handle is active. + mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f ); + } } + + // Checks if there is enough space above the text control. If not it places the popup under it. + GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox ); + + SetUpPopupPositionNotifications(); + + mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position ); + mPopupSetNewPosition = false; + } + + void PopupRelayoutComplete( Actor actor ) + { + // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box. + + DeterminePositionPopup(); } - void CreateCursor( ImageActor& cursor ) + void CreateCursor( Control& cursor, const Vector4& color ) { - cursor = CreateSolidColorActor( Color::WHITE ); + cursor = Control::New(); + cursor.SetBackgroundColor( color ); cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); - cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER ); + cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); } // Add or Remove cursor(s) from parent @@ -261,22 +536,32 @@ struct Decorator::Impl : public ConnectionTracker { if( mActiveCursor == ACTIVE_CURSOR_NONE ) { - UnparentAndReset( mPrimaryCursor ); - UnparentAndReset( mSecondaryCursor ); + if( mPrimaryCursor ) + { + mPrimaryCursor.Unparent(); + } + if( mSecondaryCursor ) + { + mSecondaryCursor.Unparent(); + } } else { - /* Create Primary and or Secondary Cursor(s) if active and add to parent */ + // Create Primary and or Secondary Cursor(s) if active and add to parent if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY || mActiveCursor == ACTIVE_CURSOR_BOTH ) { if ( !mPrimaryCursor ) { - CreateCursor( mPrimaryCursor ); + CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color ); #ifdef DECORATOR_DEBUG mPrimaryCursor.SetName( "PrimaryCursorActor" ); #endif - mActiveLayer.Add( mPrimaryCursor); + } + + if( !mPrimaryCursor.GetParent() ) + { + mActiveLayer.Add( mPrimaryCursor ); } } @@ -284,11 +569,22 @@ struct Decorator::Impl : public ConnectionTracker { if ( !mSecondaryCursor ) { - CreateCursor( mSecondaryCursor ); + CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color ); #ifdef DECORATOR_DEBUG mSecondaryCursor.SetName( "SecondaryCursorActor" ); #endif - mActiveLayer.Add( mSecondaryCursor); + } + + if( !mSecondaryCursor.GetParent() ) + { + mActiveLayer.Add( mSecondaryCursor ); + } + } + else + { + if( mSecondaryCursor ) + { + mSecondaryCursor.Unparent(); } } } @@ -296,395 +592,1140 @@ struct Decorator::Impl : public ConnectionTracker bool OnCursorBlinkTimerTick() { - // Cursor blinking - if ( mPrimaryCursor ) + if( !mDelayCursorBlink ) { - mPrimaryCursor.SetVisible( mCursorBlinkStatus ); + // Cursor blinking + if ( mPrimaryCursor ) + { + mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus ); + } + if ( mSecondaryCursor ) + { + mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus ); + } + + mCursorBlinkStatus = !mCursorBlinkStatus; } - if ( mSecondaryCursor ) + else { - mSecondaryCursor.SetVisible( mCursorBlinkStatus ); + // Resume blinking + mDelayCursorBlink = false; } - mCursorBlinkStatus = !mCursorBlinkStatus; - return true; } void SetupTouchEvents() { - if ( !mTapDetector ) - { - mTapDetector = TapGestureDetector::New(); - mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap ); - } + mTapDetector = TapGestureDetector::New(); + mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap ); - if ( !mPanGestureDetector ) - { - mPanGestureDetector = PanGestureDetector::New(); - mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan ); - } + mPanGestureDetector = PanGestureDetector::New(); + mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan ); } void CreateActiveLayer() { if( !mActiveLayer ) { - Actor parent = mTextControlParent.Self(); - mActiveLayer = Layer::New(); #ifdef DECORATOR_DEBUG mActiveLayer.SetName ( "ActiveLayerActor" ); #endif - mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER); - mActiveLayer.SetParentOrigin( ParentOrigin::CENTER); - mActiveLayer.SetSizeMode( SIZE_EQUAL_TO_PARENT ); - mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION ); + mActiveLayer.SetParentOrigin( ParentOrigin::CENTER ); + mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS ); - parent.Add( mActiveLayer ); + // Add the active layer telling the controller it doesn't need clipping. + mController.AddDecoration( mActiveLayer, false ); } mActiveLayer.RaiseToTop(); } + void SetSelectionHandleMarkerSize( HandleImpl& handle ) + { + if( handle.markerActor ) + { + handle.markerActor.SetSize( 0, handle.lineHeight ); + } + } + void CreateGrabHandle() { - if( !mGrabHandle ) + HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + if( !grabHandle.actor ) { - if ( !mGrabHandleImage ) + if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] ) { - mGrabHandleImage = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE ); - } - - mGrabHandle = ImageActor::New( mGrabHandleImage ); -#ifdef DECORATOR_DEBUG - mGrabHandle.SetName( "GrabHandleActor" ); -#endif - mGrabHandle.SetParentOrigin( ParentOrigin::TOP_LEFT ); - mGrabHandle.SetAnchorPoint( AnchorPoint::TOP_CENTER ); - mGrabHandle.SetDrawMode( DrawMode::OVERLAY ); + grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] ); + GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION ); + grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER ); - // Area that Grab handle responds to, larger than actual handle so easier to move + // Area that Grab handle responds to, larger than actual handle so easier to move #ifdef DECORATOR_DEBUG - mGrabArea = Toolkit::CreateSolidColorActor( Vector4(1.0f, 0.0f, 0.0f, 0.5f) ); - mGrabArea.SetName( "GrabArea" ); + grabHandle.actor.SetName( "GrabHandleActor" ); + if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) ) + { + grabHandle.grabArea = Control::New(); + Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea ); + control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) ); + grabHandle.grabArea.SetName( "GrabArea" ); + } + else + { + grabHandle.grabArea = Actor::New(); + grabHandle.grabArea.SetName( "GrabArea" ); + } #else - mGrabArea = Actor::New(); + grabHandle.grabArea = Actor::New(); #endif - mGrabArea.SetParentOrigin( ParentOrigin::TOP_CENTER ); - mGrabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER ); - mGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT ); - mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ); - mGrabHandle.Add(mGrabArea); - mTapDetector.Attach( mGrabArea ); - mPanGestureDetector.Attach( mGrabArea ); + grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER ); + grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER ); + grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS ); + grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ); + grabHandle.actor.Add( grabHandle.grabArea ); + grabHandle.actor.SetColor( mHandleColor ); + + grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched ); + mTapDetector.Attach( grabHandle.grabArea ); + mPanGestureDetector.Attach( grabHandle.grabArea ); + + mActiveLayer.Add( grabHandle.actor ); + } + } - mActiveLayer.Add(mGrabHandle); + if( grabHandle.actor && !grabHandle.actor.GetParent() ) + { + mActiveLayer.Add( grabHandle.actor ); } } - void CreateSelectionHandles() + void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType ) { - SelectionHandleImpl& primary = mSelectionHandle[ PRIMARY_SELECTION_HANDLE ]; - if ( !primary.actor ) + if( image ) { - if ( !primary.releasedImage ) + handle.markerActor = ImageView::New( image ); + handle.markerActor.SetColor( mHandleColor ); + handle.actor.Add( handle.markerActor ); + + handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT ); + + if( LEFT_SELECTION_HANDLE == handleType ) + { + handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT ); + handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT ); + } + else if( RIGHT_SELECTION_HANDLE == handleType ) { - primary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE ); + handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT ); + handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT ); } + } + } - primary.actor = ImageActor::New( primary.releasedImage ); + void CreateSelectionHandles() + { + HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ]; + if( !primary.actor ) + { + if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ) + { + primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); #ifdef DECORATOR_DEBUG - primary.actor.SetName("SelectionHandleOne"); + primary.actor.SetName("SelectionHandleOne"); #endif - primary.actor.SetParentOrigin( ParentOrigin::TOP_LEFT ); - primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text. - primary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text - primary.flipped = false; + primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text. + GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION ); + primary.actor.SetColor( mHandleColor ); - primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move + primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move #ifdef DECORATOR_DEBUG - primary.grabArea.SetName("SelectionHandleOneGrabArea"); + primary.grabArea.SetName("SelectionHandleOneGrabArea"); #endif - primary.grabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT ); - primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ); - primary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION ); + primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS ); + primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER ); + primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER ); + primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ); + + mTapDetector.Attach( primary.grabArea ); + mPanGestureDetector.Attach( primary.grabArea ); + primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched ); + + primary.actor.Add( primary.grabArea ); - mTapDetector.Attach( primary.grabArea ); - mPanGestureDetector.Attach( primary.grabArea ); - primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched ); + CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE ); + } + } - primary.actor.Add( primary.grabArea ); + if( primary.actor && !primary.actor.GetParent() ) + { mActiveLayer.Add( primary.actor ); } - SelectionHandleImpl& secondary = mSelectionHandle[ SECONDARY_SELECTION_HANDLE ]; - if ( !secondary.actor ) + HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ]; + if( !secondary.actor ) { - if ( !secondary.releasedImage ) + if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ) { - secondary.releasedImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO ); - } - - secondary.actor = ImageActor::New( secondary.releasedImage ); + secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); #ifdef DECORATOR_DEBUG - secondary.actor.SetName("SelectionHandleTwo"); + secondary.actor.SetName("SelectionHandleTwo"); #endif - secondary.actor.SetParentOrigin( ParentOrigin::TOP_LEFT ); - secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text. - secondary.actor.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text - secondary.flipped = false; + secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text. + GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION ); + secondary.actor.SetColor( mHandleColor ); - secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move + secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move #ifdef DECORATOR_DEBUG - secondary.grabArea.SetName("SelectionHandleTwoGrabArea"); + secondary.grabArea.SetName("SelectionHandleTwoGrabArea"); #endif - secondary.grabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT ); - secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ); - secondary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION ); + secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS ); + secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER ); + secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER ); + secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ); - mTapDetector.Attach( secondary.grabArea ); - mPanGestureDetector.Attach( secondary.grabArea ); - secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched ); + mTapDetector.Attach( secondary.grabArea ); + mPanGestureDetector.Attach( secondary.grabArea ); + secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched ); - secondary.actor.Add( secondary.grabArea ); - mActiveLayer.Add( secondary.actor ); - } + secondary.actor.Add( secondary.grabArea ); - //SetUpHandlePropertyNotifications(); TODO - } + CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE ); + } + } - void CreateHighlight() - { - if ( !mHighlightMeshActor ) + if( secondary.actor && !secondary.actor.GetParent() ) { - mHighlightMaterial = Material::New( "HighlightMaterial" ); - mHighlightMaterial.SetDiffuseColor( mHighlightColor ); - - mHighlightMeshData.SetMaterial( mHighlightMaterial ); - mHighlightMeshData.SetHasNormals( true ); - - mHighlightMesh = Mesh::New( mHighlightMeshData ); - - mHighlightMeshActor = MeshActor::New( mHighlightMesh ); -#ifdef DECORATOR_DEBUG - mHighlightMeshActor.SetName( "HighlightMeshActor" ); -#endif - mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT ); - mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); - mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET ); - - Actor parent = mTextControlParent.Self(); - parent.Add( mHighlightMeshActor ); + mActiveLayer.Add( secondary.actor ); } } - void UpdateHighlight() + void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position ) { - // Construct a Mesh with a texture to be used as the highlight 'box' for selected text - // - // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads. - // - // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ] - // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ] - // [ BOTTOM] [ MIDDLE ] [ MIDDLE ] - // [BOTTOM] [ MIDDLE ] - // [BOTTOM] - // - // Each quad is created as 2 triangles. - // Middle is just 1 quad regardless of its size. - // - // (0,0) (0,0) - // 0* *2 0* *2 - // TOP TOP - // 3* *1 3* *1 - // 4* *1 4* *6 - // MIDDLE BOTTOM - // 6* *5 7* *5 - // 6* *8 - // BOTTOM - // 9* *7 - // - - if ( mHighlightMesh && mHighlightMaterial && !mHighlightQuadList.empty() ) - { - MeshData::VertexContainer vertices; - Dali::MeshData::FaceIndices faceIndices; - - std::vector::iterator iter = mHighlightQuadList.begin(); - std::vector::iterator endIter = mHighlightQuadList.end(); - - // vertex position defaults to (0 0 0) - MeshData::Vertex vertex; - // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor. - vertex.nZ = 1.0f; - - for(std::size_t v = 0; iter != endIter; ++iter,v+=4 ) - { - // Add each quad geometry (a sub-selection) to the mesh data. - - // 0-----1 - // |\ | - // | \ A | - // | \ | - // | B \ | - // | \| - // 2-----3 - - QuadCoordinates& quad = *iter; - // top-left (v+0) - vertex.x = quad.min.x; - vertex.y = quad.min.y; - vertices.push_back( vertex ); - - // top-right (v+1) - vertex.x = quad.max.x; - vertex.y = quad.min.y; - vertices.push_back( vertex ); - - // bottom-left (v+2) - vertex.x = quad.min.x; - vertex.y = quad.max.y; - vertices.push_back( vertex ); - - // bottom-right (v+3) - vertex.x = quad.max.x; - vertex.y = quad.max.y; - vertices.push_back( vertex ); - - // triangle A (3, 1, 0) - faceIndices.push_back( v + 3 ); - faceIndices.push_back( v + 1 ); - faceIndices.push_back( v ); - - // triangle B (0, 2, 3) - faceIndices.push_back( v ); - faceIndices.push_back( v + 2 ); - faceIndices.push_back( v + 3 ); - - mHighlightMeshData.SetFaceIndices( faceIndices ); - } - - BoneContainer bones(0); // passed empty as bones not required - mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial ); - mHighlightMesh.UpdateMeshData( mHighlightMeshData ); - } + // Gets the world position of the active layer. The active layer is where the handles are added. + const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition(); + + // The grab handle position in world coords. + // The active layer's world position is the center of the active layer. The origin of the + // coord system of the handles is the top left of the active layer. + position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x; + position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y; } - void OnTap( Actor actor, const TapGesture& tap ) + void SetGrabHandlePosition() { - if( actor == mGrabHandle ) + // Reference to the grab handle. + HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + + // Transforms the handle position into world coordinates. + // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition() + // as it's transforming the handle's position set by the text-controller and not + // the final position set to the actor. Another difference is the GetCurrentWorldPosition() + // retrieves the position of the center of the actor but the handle's position set + // by the text controller is not the center of the actor. + Vector2 grabHandleWorldPosition; + CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition ); + + // Check if the grab handle exceeds the boundaries of the decoration box. + // At the moment only the height is checked for the grab handle. + + grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred && + ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) || + ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w ); + + // The grab handle 'y' position in local coords. + // If the grab handle exceeds the bottom of the decoration box, + // set the 'y' position to the top of the line. + // The SetGrabHandleImage() method will change the orientation. + const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight; + + if( grabHandle.actor ) { - // TODO + grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ), + yLocalPosition ); // TODO : Fix for multiline. } } - void OnPan( Actor actor, const PanGesture& gesture ) + void SetSelectionHandlePosition( HandleType type ) { - if( actor == mGrabArea ) + const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type; + + // Reference to the selection handle. + HandleImpl& handle = mHandle[type]; + + // Transforms the handle position into world coordinates. + // @note This is not the same value as handle.actor.GetCurrentWorldPosition() + // as it's transforming the handle's position set by the text-controller and not + // the final position set to the actor. Another difference is the GetCurrentWorldPosition() + // retrieves the position of the center of the actor but the handle's position set + // by the text controller is not the center of the actor. + Vector2 handleWorldPosition; + CalculateHandleWorldCoordinates( handle, handleWorldPosition ); + + // Whether to flip the handle (horizontally). + bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection; + + // Whether to flip the handles if they are crossed. + bool crossFlip = false; + if( mFlipSelectionHandlesOnCross || !mHandlePanning ) { - if( Gesture::Started == gesture.state ) - { - mGrabDisplacementX = mGrabDisplacementY = 0; - } + crossFlip = mHandleCurrentCrossed; + } - mGrabDisplacementX += gesture.displacement.x; - mGrabDisplacementY += gesture.displacement.y; + // Does not flip if both conditions are true (double flip) + flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed ); - float x = mCursor[PRIMARY_CURSOR].x + mGrabDisplacementX; - float y = mCursor[PRIMARY_CURSOR].y + mCursor[PRIMARY_CURSOR].height*0.5f + mGrabDisplacementY; + // Will flip the handles vertically if the user prefers it. + bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred; - if( Gesture::Started == gesture.state || - Gesture::Continuing == gesture.state ) + if( crossFlip || mHandlePreviousCrossed ) + { + if( isPrimaryHandle ) { - mObserver.GrabHandleEvent( GRAB_HANDLE_PRESSED, x, y ); + verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred; } - else if( Gesture::Finished == gesture.state || - Gesture::Cancelled == gesture.state ) + else { - mObserver.GrabHandleEvent( GRAB_HANDLE_RELEASED, x, y ); + verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred; } } - } - bool OnHandleOneTouched( Actor actor, const TouchEvent& touch ) - { - // TODO - return false; - } + // Check if the selection handle exceeds the boundaries of the decoration box. + const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x ); + const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z ); - bool OnHandleTwoTouched( Actor actor, const TouchEvent& touch ) - { - // TODO - return false; - } + // Does not flip if both conditions are true (double flip) + flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge ); + if( flipHandle ) + { + if( handle.actor && !handle.horizontallyFlipped ) + { + // Change the anchor point to flip the image. + handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT ); - Internal::Control& mTextControlParent; - Observer& mObserver; + handle.horizontallyFlipped = true; + } + } + else + { + if( handle.actor && handle.horizontallyFlipped ) + { + // Reset the anchor point. + handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT ); - Layer mActiveLayer; // Layer for active handles and alike that ensures they are above all else. + handle.horizontallyFlipped = false; + } + } - unsigned int mActiveCursor; - bool mActiveGrabHandle; - bool mActiveSelection; - bool mActiveCopyPastePopup; + // Whether to flip the handle vertically. + handle.verticallyFlipped = ( verticallyFlippedPreferred && + ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) || + ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w ); - CursorImpl mCursor[CURSOR_COUNT]; + // The primary selection handle 'y' position in local coords. + // If the handle exceeds the bottom of the decoration box, + // set the 'y' position to the top of the line. + // The SetHandleImage() method will change the orientation. + const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight; - Timer mCursorBlinkTimer; // Timer to signal cursor to blink - unsigned int mCursorBlinkInterval; - float mCursorBlinkDuration; - bool mCursorBlinkStatus; // Flag to switch between blink on and blink off + if( handle.actor ) + { + handle.actor.SetPosition( handle.position.x, + yLocalPosition ); // TODO : Fix for multiline. + } + } - ImageActor mPrimaryCursor; - ImageActor mSecondaryCursor; + void SetHandleImage( HandleType type ) + { + HandleImpl& handle = mHandle[type]; - ImageActor mGrabHandle; - Actor mGrabArea; - float mGrabDisplacementX; - float mGrabDisplacementY; + HandleType markerType = HANDLE_TYPE_COUNT; + // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle. + if( LEFT_SELECTION_HANDLE == type ) + { + type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE; + markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER; + } + else if( RIGHT_SELECTION_HANDLE == type ) + { + type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE; + markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER; + } - SelectionHandleImpl mSelectionHandle[SELECTION_HANDLE_COUNT]; + // Chooses between the released or pressed image. It checks whether the pressed image exists. + if( handle.actor ) + { + const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED ); - MeshActor mHighlightMeshActor; ///< Mesh Actor to display highlight - Mesh mHighlightMesh; ///< Mesh for highlight - MeshData mHighlightMeshData; ///< Mesh Data for highlight - Material mHighlightMaterial; ///< Material used for highlight - Vector4 mHighlightColor; ///< Color of the highlight - QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight + handle.actor.SetImage( mHandleImages[type][imageType] ); + } - TextSelectionPopup mCopyPastePopup; + if( HANDLE_TYPE_COUNT != markerType ) + { + if( handle.markerActor ) + { + const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED ); + handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] ); + } + } - Image mCursorImage; - Image mGrabHandleImage; + // Whether to flip the handle vertically. + if( handle.actor ) + { + handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS ); + } + } - TapGestureDetector mTapDetector; - PanGestureDetector mPanGestureDetector; + void CreateHighlight() + { + if( !mHighlightActor ) + { + mHighlightActor = Actor::New(); - Rect mBoundingBox; -}; +#ifdef DECORATOR_DEBUG + mHighlightActor.SetName( "HighlightActor" ); +#endif + mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + mHighlightActor.SetSize( 1.0f, 1.0f ); + mHighlightActor.SetColor( mHighlightColor ); + mHighlightActor.SetColorMode( USE_OWN_COLOR ); + } -DecoratorPtr Decorator::New( Internal::Control& parent, Observer& observer ) -{ - return DecoratorPtr( new Decorator(parent, observer) ); -} + // Add the highlight box telling the controller it needs clipping. + mController.AddDecoration( mHighlightActor, true ); + } -void Decorator::SetBoundingBox( const Rect& boundingBox ) -{ - mImpl->mBoundingBox = boundingBox; -} + void UpdateHighlight() + { + if ( mHighlightActor ) + { + if( !mHighlightQuadList.empty() ) + { + Vector< Vector2 > vertices; + Vector< unsigned int> indices; + Vector2 vertex; + + std::vector::iterator iter = mHighlightQuadList.begin(); + std::vector::iterator endIter = mHighlightQuadList.end(); + + for( std::size_t v = 0; iter != endIter; ++iter,v+=4 ) + { + QuadCoordinates& quad = *iter; + + // top-left (v+0) + vertex.x = quad.min.x; + vertex.y = quad.min.y; + vertices.PushBack( vertex ); + + // top-right (v+1) + vertex.x = quad.max.x; + vertex.y = quad.min.y; + vertices.PushBack( vertex ); + + // bottom-left (v+2) + vertex.x = quad.min.x; + vertex.y = quad.max.y; + vertices.PushBack( vertex ); + + // bottom-right (v+3) + vertex.x = quad.max.x; + vertex.y = quad.max.y; + vertices.PushBack( vertex ); + + // triangle A (3, 1, 0) + indices.PushBack( v + 3 ); + indices.PushBack( v + 1 ); + indices.PushBack( v ); + + // triangle B (0, 2, 3) + indices.PushBack( v ); + indices.PushBack( v + 2 ); + indices.PushBack( v + 3 ); + } + + if( ! mQuadVertices ) + { + mQuadVertices = PropertyBuffer::New( mQuadVertexFormat ); + } + + if( ! mQuadIndices ) + { + mQuadIndices = PropertyBuffer::New( mQuadIndexFormat ); + } + + mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() ); + mQuadIndices.SetData( &indices[ 0 ], indices.Size() ); + + if( !mQuadGeometry ) + { + mQuadGeometry = Geometry::New(); + mQuadGeometry.AddVertexBuffer( mQuadVertices ); + } + mQuadGeometry.SetIndexBuffer( mQuadIndices ); + + if( !mHighlightRenderer ) + { + mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightShader ); + mHighlightActor.AddRenderer( mHighlightRenderer ); + } + } + + mHighlightActor.SetPosition( mHighlightPosition.x, + mHighlightPosition.y ); + + mHighlightQuadList.clear(); -const Rect& Decorator::GetBoundingBox() const + if( mHighlightRenderer ) + { + mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u. + } + } + } + + void OnTap( Actor actor, const TapGesture& tap ) + { + if( actor == mHandle[GRAB_HANDLE].actor ) + { + // TODO + } + } + + void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture ) + { + if( Gesture::Started == gesture.state ) + { + handle.grabDisplacementX = handle.grabDisplacementY = 0; + } + + handle.grabDisplacementX += gesture.displacement.x; + handle.grabDisplacementY += gesture.displacement.y; + + const float x = handle.position.x + handle.grabDisplacementX; + const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY; + + if( Gesture::Started == gesture.state || + Gesture::Continuing == gesture.state ) + { + Vector2 targetSize; + mController.GetTargetSize( targetSize ); + + if( x < mScrollThreshold ) + { + mScrollDirection = SCROLL_RIGHT; + mHandleScrolling = type; + StartScrollTimer(); + } + else if( x > targetSize.width - mScrollThreshold ) + { + mScrollDirection = SCROLL_LEFT; + mHandleScrolling = type; + StartScrollTimer(); + } + else + { + mHandleScrolling = HANDLE_TYPE_COUNT; + StopScrollTimer(); + mController.DecorationEvent( type, HANDLE_PRESSED, x, y ); + } + + mHandlePanning = true; + } + else if( Gesture::Finished == gesture.state || + Gesture::Cancelled == gesture.state ) + { + if( mScrollTimer && + ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) ) + { + mNotifyEndOfScroll = false; + mHandleScrolling = HANDLE_TYPE_COUNT; + StopScrollTimer(); + mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y ); + } + else + { + mController.DecorationEvent( type, HANDLE_RELEASED, x, y ); + } + + if( handle.actor ) + { + handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] ); + } + handle.pressed = false; + + mHandlePanning = false; + } + } + + void OnPan( Actor actor, const PanGesture& gesture ) + { + HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE]; + HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE]; + + if( actor == grabHandle.grabArea ) + { + DoPan( grabHandle, GRAB_HANDLE, gesture ); + } + else if( actor == primarySelectionHandle.grabArea ) + { + DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture ); + } + else if( actor == secondarySelectionHandle.grabArea ) + { + DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture ); + } + } + + bool OnGrabHandleTouched( Actor actor, const TouchEvent& event ) + { + // Switch between pressed/release grab-handle images + if( event.GetPointCount() > 0 && + mHandle[GRAB_HANDLE].actor ) + { + const TouchPoint& point = event.GetPoint(0); + + if( TouchPoint::Down == point.state ) + { + mHandle[GRAB_HANDLE].pressed = true; + } + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) + { + mHandle[GRAB_HANDLE].pressed = false; + } + + SetHandleImage( GRAB_HANDLE ); + } + + // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area + return true; + } + + bool OnHandleOneTouched( Actor actor, const TouchEvent& event ) + { + // Switch between pressed/release selection handle images + if( event.GetPointCount() > 0 && + mHandle[LEFT_SELECTION_HANDLE].actor ) + { + const TouchPoint& point = event.GetPoint(0); + + if( TouchPoint::Down == point.state ) + { + mHandle[LEFT_SELECTION_HANDLE].pressed = true; + } + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) + { + mHandle[LEFT_SELECTION_HANDLE].pressed = false; + mHandlePreviousCrossed = mHandleCurrentCrossed; + mHandlePanning = false; + } + + SetHandleImage( LEFT_SELECTION_HANDLE ); + } + + // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area + return true; + } + + bool OnHandleTwoTouched( Actor actor, const TouchEvent& event ) + { + // Switch between pressed/release selection handle images + if( event.GetPointCount() > 0 && + mHandle[RIGHT_SELECTION_HANDLE].actor ) + { + const TouchPoint& point = event.GetPoint(0); + + if( TouchPoint::Down == point.state ) + { + mHandle[RIGHT_SELECTION_HANDLE].pressed = true; + } + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) + { + mHandle[RIGHT_SELECTION_HANDLE].pressed = false; + mHandlePreviousCrossed = mHandleCurrentCrossed; + mHandlePanning = false; + } + + SetHandleImage( RIGHT_SELECTION_HANDLE ); + } + + // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area + return true; + } + + void HandleResetPosition( PropertyNotification& source ) + { + const HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + + if( grabHandle.active ) + { + // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box. + SetGrabHandlePosition(); + + // Sets the grab handle image according if it's pressed, flipped, etc. + SetHandleImage( GRAB_HANDLE ); + } + else + { + // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box. + SetSelectionHandlePosition( LEFT_SELECTION_HANDLE ); + + // Sets the primary handle image according if it's pressed, flipped, etc. + SetHandleImage( LEFT_SELECTION_HANDLE ); + + // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box. + SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE ); + + // Sets the secondary handle image according if it's pressed, flipped, etc. + SetHandleImage( RIGHT_SELECTION_HANDLE ); + } + } + + void SetupActiveLayerPropertyNotifications() + { + if( !mActiveLayer ) + { + return; + } + + // Vertical notifications. + + // Disconnect any previous connected callback. + if( mVerticalLessThanNotification ) + { + mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition ); + mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification ); + } + + if( mVerticalGreaterThanNotification ) + { + mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition ); + mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification ); + } + + const HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE]; + const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE]; + + if( grabHandle.active ) + { + if( grabHandle.verticallyFlipped ) + { + // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display. + mVerticalGreaterThanNotification.Reset(); + + // The vertical distance from the center of the active layer to the top edje of the display. + const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height; + + mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + LessThanCondition( mBoundingBox.y + topHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + else + { + // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display. + mVerticalLessThanNotification.Reset(); + + // The vertical distance from the center of the active layer to the bottom edje of the display. + const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height; + + mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + GreaterThanCondition( mBoundingBox.w - bottomHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + } + else // The selection handles are active + { + if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped ) + { + // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display. + mVerticalGreaterThanNotification.Reset(); + + // The vertical distance from the center of the active layer to the top edje of the display. + const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height ); + + mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + LessThanCondition( mBoundingBox.y + topHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped ) + { + // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display. + mVerticalLessThanNotification.Reset(); + + // The vertical distance from the center of the active layer to the bottom edje of the display. + const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height, + secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height ); + + mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + GreaterThanCondition( mBoundingBox.w - bottomHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + else + { + // Only one of the selection handles is vertically flipped. Both vertical notifications are needed. + + // The vertical distance from the center of the active layer to the top edje of the display. + const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ? + -primaryHandle.position.y + primaryHandle.size.height : + -secondaryHandle.position.y + secondaryHandle.size.height ); + + mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + LessThanCondition( mBoundingBox.y + topHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + + // The vertical distance from the center of the active layer to the bottom edje of the display. + const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ? + secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height : + primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height ); + + mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + GreaterThanCondition( mBoundingBox.w - bottomHeight ) ); + + // Notifies the change from false to true and from true to false. + mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + } + + // Horizontal notifications. + + // Disconnect any previous connected callback. + if( mHorizontalLessThanNotification ) + { + mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition ); + mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification ); + } + + if( mHorizontalGreaterThanNotification ) + { + mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition ); + mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification ); + } + + if( primaryHandle.active || secondaryHandle.active ) + { + // The horizontal distance from the center of the active layer to the left edje of the display. + const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width, + -secondaryHandle.position.x + secondaryHandle.size.width ); + + mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, + LessThanCondition( mBoundingBox.x + leftWidth ) ); + + // Notifies the change from false to true and from true to false. + mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + + // The horizontal distance from the center of the active layer to the right edje of the display. + const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width, + secondaryHandle.position.x + secondaryHandle.size.width ); + + mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, + GreaterThanCondition( mBoundingBox.z - rightWidth ) ); + + // Notifies the change from false to true and from true to false. + mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged ); + + // Connects the signals with the callbacks. + mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition ); + } + } + + // Popup + + float AlternatePopUpPositionRelativeToCursor() + { + float alternativePosition = 0.0f; + + const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ); + + const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE]; + const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE]; + const HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + const CursorImpl& cursor = mCursor[PRIMARY_CURSOR]; + + if( primaryHandle.active || secondaryHandle.active ) + { + const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height ); + alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y ); + } + else + { + alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y; + } + + return alternativePosition; + } + + void PopUpLeavesVerticalBoundary( PropertyNotification& source ) + { + float alternativeYPosition = 0.0f; + // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted + // if can't be positioned above, then position below row. + alternativeYPosition = AlternatePopUpPositionRelativeToCursor(); + + mCopyPastePopup.actor.SetY( alternativeYPosition ); + } + + void SetUpPopupPositionNotifications() + { + // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left. + + // Exceeding vertical boundary + + const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ); + + PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, + OutsideCondition( mBoundingBox.y + popupHeight * 0.5f, + mBoundingBox.w - popupHeight * 0.5f ) ); + + verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary ); + } + + void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld ) + { + DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() ) + + // Parent must already by added to Stage for these Get calls to work + const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint(); + const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position + + // Calculate distance to move popup (in local space) so fits within the boundary + float xOffSetToKeepWithinBounds = 0.0f; + if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x ) + { + xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x ); + } + else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z ) + { + xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x ); + } + + // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind. + if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y ) + { + requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor(); + } + + requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds; + + // Prevent pixel mis-alignment by rounding down. + requiredPopupPosition.x = floor( requiredPopupPosition.x ); + requiredPopupPosition.y = floor( requiredPopupPosition.y ); + } + + void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image ) + { + HandleImpl& handle = mHandle[handleType]; + handle.size = Size( image.GetWidth(), image.GetHeight() ); + + mHandleImages[handleType][handleImageType] = image; + } + + void SetScrollThreshold( float threshold ) + { + mScrollThreshold = threshold; + } + + float GetScrollThreshold() const + { + return mScrollThreshold; + } + + void SetScrollSpeed( float speed ) + { + mScrollSpeed = speed; + mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS; + } + + float GetScrollSpeed() const + { + return mScrollSpeed; + } + + void NotifyEndOfScroll() + { + StopScrollTimer(); + + if( mScrollTimer ) + { + mNotifyEndOfScroll = true; + } + } + + /** + * Creates and starts a timer to scroll the text when handles are close to the edges of the text. + * + * It only starts the timer if it's already created. + */ + void StartScrollTimer() + { + if( !mScrollTimer ) + { + mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL ); + mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick ); + } + + if( !mScrollTimer.IsRunning() ) + { + mScrollTimer.Start(); + } + } + + /** + * Stops the timer used to scroll the text. + */ + void StopScrollTimer() + { + if( mScrollTimer ) + { + mScrollTimer.Stop(); + } + } + + /** + * Callback called by the timer used to scroll the text. + * + * It calculates and sets a new scroll position. + */ + bool OnScrollTimerTick() + { + if( HANDLE_TYPE_COUNT != mHandleScrolling ) + { + mController.DecorationEvent( mHandleScrolling, + HANDLE_SCROLLING, + mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance, + 0.f ); + } + + return true; + } + + ControllerInterface& mController; + + TapGestureDetector mTapDetector; + PanGestureDetector mPanGestureDetector; + Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink + Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges. + + Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else. + PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value. + PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value. + PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value. + PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value. + Control mPrimaryCursor; + Control mSecondaryCursor; + + Actor mHighlightActor; ///< Actor to display highlight + Renderer mHighlightRenderer; + Shader mHighlightShader; ///< Shader used for highlight + Property::Map mQuadVertexFormat; + Property::Map mQuadIndexFormat; + PopupImpl mCopyPastePopup; + TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons + TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface; + + Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT]; + Vector4 mHandleColor; + + CursorImpl mCursor[CURSOR_COUNT]; + HandleImpl mHandle[HANDLE_TYPE_COUNT]; + + PropertyBuffer mQuadVertices; + PropertyBuffer mQuadIndices; + Geometry mQuadGeometry; + QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight + + Vector4 mBoundingBox; ///< The bounding box in world coords. + Vector4 mHighlightColor; ///< Color of the highlight + Vector2 mHighlightPosition; ///< The position of the highlight actor. + Vector2 mControlSize; ///< The control's size. Set by the Relayout. + + unsigned int mActiveCursor; + unsigned int mCursorBlinkInterval; + float mCursorBlinkDuration; + float mCursorWidth; ///< The width of the cursors in pixels. + HandleType mHandleScrolling; ///< The handle which is scrolling. + ScrollDirection mScrollDirection; ///< The direction of the scroll. + float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events. + float mScrollSpeed; ///< The scroll speed in pixels per second. + float mScrollDistance; ///< Distance the text scrolls during a scroll interval. + int mTextDepth; ///< The depth used to render the text. + + bool mActiveCopyPastePopup : 1; + bool mPopupSetNewPosition : 1; + bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off. + bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text. + bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible. + bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible. + bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross. + bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction. + bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction. + bool mHandlePanning : 1; ///< Whether any of the handles is moving. + bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed. + bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up. + bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll. +}; + +DecoratorPtr Decorator::New( ControllerInterface& controller, + TextSelectionPopupCallbackInterface& callbackInterface ) +{ + return DecoratorPtr( new Decorator( controller, + callbackInterface ) ); +} + +void Decorator::SetBoundingBox( const Rect& boundingBox ) { - return mImpl->mBoundingBox; + LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox ); } -void Decorator::Relayout( const Vector2& size, const Vector2& scrollPosition ) +void Decorator::GetBoundingBox( Rect& boundingBox ) const { - mImpl->Relayout( size, scrollPosition ); + WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox ); +} + +void Decorator::Relayout( const Vector2& size ) +{ + mImpl->Relayout( size ); +} + +void Decorator::UpdatePositions( const Vector2& scrollOffset ) +{ + mImpl->UpdatePositions( scrollOffset ); } /** Cursor **/ @@ -699,25 +1740,28 @@ unsigned int Decorator::GetActiveCursor() const return mImpl->mActiveCursor; } -void Decorator::SetPosition( Cursor cursor, float x, float y, float height ) +void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight ) { - // Adjust grab handle displacement - mImpl->mGrabDisplacementX -= x - mImpl->mCursor[cursor].x; - mImpl->mGrabDisplacementY -= y - mImpl->mCursor[cursor].y; + mImpl->mCursor[cursor].position.x = x; + mImpl->mCursor[cursor].position.y = y; + mImpl->mCursor[cursor].cursorHeight = cursorHeight; + mImpl->mCursor[cursor].lineHeight = lineHeight; +} - mImpl->mCursor[cursor].x = x; - mImpl->mCursor[cursor].y = y; - mImpl->mCursor[cursor].height = height; +void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const +{ + x = mImpl->mCursor[cursor].position.x; + y = mImpl->mCursor[cursor].position.y; + cursorHeight = mImpl->mCursor[cursor].cursorHeight; + lineHeight = mImpl->mCursor[cursor].lineHeight; } -void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& height ) const +const Vector2& Decorator::GetPosition( Cursor cursor ) const { - x = mImpl->mCursor[cursor].x; - y = mImpl->mCursor[cursor].y; - height = mImpl->mCursor[cursor].height; + return mImpl->mCursor[cursor].position; } -void Decorator::SetColor( Cursor cursor, const Dali::Vector4& color ) +void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color ) { mImpl->mCursor[cursor].color = color; } @@ -747,16 +1791,24 @@ void Decorator::StopCursorBlink() { mImpl->mCursorBlinkTimer.Stop(); } + + mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown +} + +void Decorator::DelayCursorBlink() +{ + mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer + mImpl->mDelayCursorBlink = true; } void Decorator::SetCursorBlinkInterval( float seconds ) { - mImpl->mCursorBlinkInterval = seconds*MILLISECONDS; // Convert to milliseconds + mImpl->mCursorBlinkInterval = static_cast( seconds * TO_MILLISECONDS ); // Convert to milliseconds } float Decorator::GetCursorBlinkInterval() const { - return mImpl->mCursorBlinkInterval; + return static_cast( mImpl->mCursorBlinkInterval ) * TO_SECONDS; } void Decorator::SetCursorBlinkDuration( float seconds ) @@ -769,74 +1821,115 @@ float Decorator::GetCursorBlinkDuration() const return mImpl->mCursorBlinkDuration; } -/** GrabHandle **/ +void Decorator::SetCursorWidth( int width ) +{ + mImpl->mCursorWidth = static_cast( width ); +} -void Decorator::SetGrabHandleActive( bool active ) +int Decorator::GetCursorWidth() const { - mImpl->mActiveGrabHandle = active; + return static_cast( mImpl->mCursorWidth ); } -bool Decorator::IsGrabHandleActive() const +/** Handles **/ + +void Decorator::SetHandleActive( HandleType handleType, bool active ) { - return mImpl->mActiveGrabHandle; + mImpl->mHandle[handleType].active = active; + + if( !active ) + { + if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) ) + { + mImpl->mHandlePreviousCrossed = false; + } + + // TODO: this is a work-around. + // The problem is the handle actor does not receive the touch event with the Interrupt + // state when the power button is pressed and the application goes to background. + mImpl->mHandle[handleType].pressed = false; + Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED]; + ImageView imageView = mImpl->mHandle[handleType].actor; + if( imageReleased && imageView ) + { + imageView.SetImage( imageReleased ); + } + } + } -void Decorator::SetGrabHandleImage( Dali::Image image ) +bool Decorator::IsHandleActive( HandleType handleType ) const { - mImpl->mGrabHandleImage = image; + return mImpl->mHandle[handleType].active ; } -Dali::Image Decorator::GetGrabHandleImage() const +void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image ) { - return mImpl->mGrabHandleImage; + mImpl->SetHandleImage( handleType, handleImageType, image ); } -/** Selection **/ +Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const +{ + return mImpl->mHandleImages[handleType][handleImageType]; +} -void Decorator::SetSelectionActive( bool active ) +void Decorator::SetHandleColor( const Vector4& color ) { - mImpl->mActiveSelection = active; + mImpl->mHandleColor = color; } -bool Decorator::IsSelectionActive() const +const Vector4& Decorator::GetHandleColor() const { - return mImpl->mActiveSelection; + return mImpl->mHandleColor; } -void Decorator::SetPosition( SelectionHandle handle, float x, float y, float height ) +void Decorator::SetPosition( HandleType handleType, float x, float y, float height ) { - mImpl->mSelectionHandle[handle].x = x; - mImpl->mSelectionHandle[handle].y = y; - mImpl->mSelectionHandle[handle].cursorHeight = height; + // Adjust handle's displacement + Impl::HandleImpl& handle = mImpl->mHandle[handleType]; + + handle.grabDisplacementX -= x - handle.position.x; + handle.grabDisplacementY -= y - handle.position.y; + + handle.position.x = x; + handle.position.y = y; + handle.lineHeight = height; } -void Decorator::GetPosition( SelectionHandle handle, float& x, float& y, float& height ) const +void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const { - x = mImpl->mSelectionHandle[handle].x; - y = mImpl->mSelectionHandle[handle].y; - height = mImpl->mSelectionHandle[handle].cursorHeight; + Impl::HandleImpl& handle = mImpl->mHandle[handleType]; + + x = handle.position.x; + y = handle.position.y; + height = handle.lineHeight; } -void Decorator::SetImage( SelectionHandle handle, SelectionHandleState state, Dali::Image image ) +const Vector2& Decorator::GetPosition( HandleType handleType ) const { - if( SELECTION_HANDLE_PRESSED == state ) - { - mImpl->mSelectionHandle[handle].pressedImage = image; - } - else - { - mImpl->mSelectionHandle[handle].releasedImage = image; - } + return mImpl->mHandle[handleType].position; } -Dali::Image Decorator::GetImage( SelectionHandle handle, SelectionHandleState state ) const +void Decorator::FlipHandleVertically( HandleType handleType, bool flip ) { - if( SELECTION_HANDLE_PRESSED == state ) - { - return mImpl->mSelectionHandle[handle].pressedImage; - } + mImpl->mHandle[handleType].verticallyFlippedPreferred = flip; +} + +bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const +{ + return mImpl->mHandle[handleType].verticallyFlippedPreferred; +} + +void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable ) +{ + mImpl->mFlipSelectionHandlesOnCross = enable; +} - return mImpl->mSelectionHandle[handle].releasedImage; +void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right ) +{ + mImpl->mHandleCurrentCrossed = indicesSwapped; + mImpl->mFlipLeftSelectionHandleDirection = left; + mImpl->mFlipRightSelectionHandleDirection = right; } void Decorator::AddHighlight( float x1, float y1, float x2, float y2 ) @@ -847,6 +1940,22 @@ 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 ) +{ + mImpl->mHighlightColor = color; +} + +const Vector4& Decorator::GetHighlightColor() const +{ + return mImpl->mHighlightColor; +} + +void Decorator::SetTextDepth( int textDepth ) +{ + mImpl->mTextDepth = textDepth; } void Decorator::SetPopupActive( bool active ) @@ -859,15 +1968,65 @@ bool Decorator::IsPopupActive() const return mImpl->mActiveCopyPastePopup ; } +void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask ) +{ + mImpl->mEnabledPopupButtons = enabledButtonsBitMask; + + if ( !mImpl->mCopyPastePopup.actor ) + { + mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface ); +#ifdef DECORATOR_DEBUG + mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup"); +#endif + mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER ); + mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation + } + + mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons ); +} + +TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons() +{ + return mImpl->mEnabledPopupButtons; +} + +/** Scroll **/ + +void Decorator::SetScrollThreshold( float threshold ) +{ + mImpl->SetScrollThreshold( threshold ); +} + +float Decorator::GetScrollThreshold() const +{ + return mImpl->GetScrollThreshold(); +} + +void Decorator::SetScrollSpeed( float speed ) +{ + mImpl->SetScrollSpeed( speed ); +} + +float Decorator::GetScrollSpeed() const +{ + return mImpl->GetScrollSpeed(); +} + +void Decorator::NotifyEndOfScroll() +{ + mImpl->NotifyEndOfScroll(); +} + Decorator::~Decorator() { delete mImpl; } -Decorator::Decorator( Dali::Toolkit::Internal::Control& parent, Observer& observer ) +Decorator::Decorator( ControllerInterface& controller, + TextSelectionPopupCallbackInterface& callbackInterface ) : mImpl( NULL ) { - mImpl = new Decorator::Impl( parent, observer ); + mImpl = new Decorator::Impl( controller, callbackInterface ); } } // namespace Text