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=bd6a8436c8582fe32a2e4d9a7c5cf4b3b1353179;hp=7934b6f1aa8c8073444a424c6520b387ba326280;hb=761fb6b07ef5754348cf0fcc468f53d32cd2a40b;hpb=8b3341983dbc29de18967db3b1e19f35bf64def1 diff --git a/dali-toolkit/internal/text/decorator/text-decorator.cpp b/dali-toolkit/internal/text/decorator/text-decorator.cpp index 7934b6f..bd6a843 100644 --- a/dali-toolkit/internal/text/decorator/text-decorator.cpp +++ b/dali-toolkit/internal/text/decorator/text-decorator.cpp @@ -20,41 +20,54 @@ // 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 +#include // INTERNAL INCLUDES -#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 @@ -72,33 +85,25 @@ Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLog // 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 char* DEFAULT_GRAB_HANDLE_IMAGE_RELEASED( DALI_IMAGE_DIR "insertpoint-icon.png" ); -const char* DEFAULT_GRAB_HANDLE_IMAGE_PRESSED( DALI_IMAGE_DIR "insertpoint-icon-pressed.png" ); -const char* DEFAULT_SELECTION_HANDLE_ONE_RELEASED( DALI_IMAGE_DIR "text-input-selection-handle-left.png" ); -const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" ); -const char* DEFAULT_SELECTION_HANDLE_TWO_RELEASED( DALI_IMAGE_DIR "text-input-selection-handle-right.png" ); -const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" ); - -const int DEFAULT_POPUP_OFFSET( -100.0f ); // Vertical offset of Popup from cursor or handles position. +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 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( (0xb2 / 255.0f), (0xeb / 255.0f), (0xf2 / 255.0f), 0.5f ); // The text highlight color. +const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 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 float DISPLAYED_HIGHLIGHT_Z_OFFSET( -0.05f ); - const unsigned int SCROLL_TICK_INTERVAL = 50u; 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. */ @@ -149,6 +154,16 @@ void LocalToWorldCoordinatesBoundingBox( const Dali::Rect& boundingRectangl 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 @@ -192,40 +207,45 @@ struct Decorator::Impl : public ConnectionTracker { HandleImpl() : position(), + size(), lineHeight( 0.0f ), grabDisplacementX( 0.f ), grabDisplacementY( 0.f ), active( false ), visible( false ), pressed( false ), - flipped( false ) + verticallyFlippedPreferred( false ), + horizontallyFlipped( false ), + verticallyFlipped( false ) { } ImageActor actor; Actor grabArea; + ImageActor markerActor; Vector2 position; - float lineHeight; ///< Not the handle height - float grabDisplacementX; - float grabDisplacementY; - bool active : 1; - bool visible : 1; - bool pressed : 1; - bool flipped : 1; + 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(), - offset( DEFAULT_POPUP_OFFSET ) + : position() { } TextSelectionPopup actor; Vector3 position; - int offset; }; Impl( ControllerInterface& controller, @@ -233,22 +253,39 @@ struct Decorator::Impl : public ConnectionTracker : mController( controller ), mEnabledPopupButtons( TextSelectionPopup::NONE ), mTextSelectionPopupCallbackInterface( callbackInterface ), - mBoundingBox( Rect() ), + 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 ), + mDelayCursorBlink( false ), mPrimaryCursorVisible( false ), mSecondaryCursorVisible( false ), - mSwapSelectionHandles( false ) + mFlipSelectionHandlesOnCross( false ), + mFlipLeftSelectionHandleDirection( false ), + mFlipRightSelectionHandleDirection( false ), + mHandlePanning( false ), + mHandleCurrentCrossed( false ), + mHandlePreviousCrossed( false ), + mNotifyEndOfScroll( false ) { + mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2; + mQuadIndexFormat[ "indices" ] = Property::INTEGER; + mHighlightMaterial = Material::New( Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ) ); + + SetupTouchEvents(); } /** @@ -262,78 +299,57 @@ struct Decorator::Impl : public ConnectionTracker // Show or hide the cursors CreateCursors(); + if( mPrimaryCursor ) { const CursorImpl& cursor = mCursor[PRIMARY_CURSOR]; - mPrimaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f ); + mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= size.width ) && ( cursor.position.x >= 0.f ); if( mPrimaryCursorVisible ) { - Vector2 position = cursor.position; - if( GRAB_HANDLE == mHandleScrolling ) - { - if( mScrollDirection == SCROLL_RIGHT ) - { - position.x = 0.f; - } - else - { - position.x = size.width; - } - } - - mPrimaryCursor.SetPosition( position.x, - position.y ); - mPrimaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) ); + mPrimaryCursor.SetPosition( cursor.position.x, + cursor.position.y ); + mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) ); } - mPrimaryCursor.SetVisible( mPrimaryCursorVisible ); + mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus ); } if( mSecondaryCursor ) { const CursorImpl& cursor = mCursor[SECONDARY_CURSOR]; - mSecondaryCursorVisible = ( cursor.position.x <= size.width ) && ( cursor.position.x >= 0.f ); + mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= size.width ) && ( cursor.position.x >= 0.f ); if( mSecondaryCursorVisible ) { mSecondaryCursor.SetPosition( cursor.position.x, cursor.position.y ); - mSecondaryCursor.SetSize( Size( 1.0f, cursor.cursorHeight ) ); + mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) ); } - mSecondaryCursor.SetVisible( mSecondaryCursorVisible ); + mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus ); } // Show or hide the grab handle HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; if( grabHandle.active ) { - Vector2 position = grabHandle.position; - - if( GRAB_HANDLE == mHandleScrolling ) - { - if( mScrollDirection == SCROLL_RIGHT ) - { - position.x = 0.f; - } - else - { - position.x = size.width; - } - } - - const bool isVisible = ( position.x <= size.width ) && ( position.x >= 0.f ); + const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= size.width ) && ( grabHandle.position.x >= 0.f ); if( isVisible ) { - SetupTouchEvents(); - CreateGrabHandle(); - grabHandle.actor.SetPosition( position.x, - position.y + grabHandle.lineHeight ); + // Sets the grab handle position and calculate 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 ); + } + + if( grabHandle.actor ) + { + grabHandle.actor.SetVisible( isVisible ); } - grabHandle.actor.SetVisible( isVisible ); } else if( grabHandle.actor ) { - UnparentAndReset( grabHandle.actor ); + grabHandle.actor.Unparent(); } // Show or hide the selection handles/highlight @@ -341,87 +357,73 @@ struct Decorator::Impl : public ConnectionTracker HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ]; if( primary.active || secondary.active ) { - Vector2 primaryPosition = primary.position; - Vector2 secondaryPosition = secondary.position; - - 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 ); + const bool isPrimaryVisible = ( primary.position.x <= size.width ) && ( primary.position.x >= 0.f ); + const bool isSecondaryVisible = ( secondary.position.x <= size.width ) && ( secondary.position.x >= 0.f ); if( isPrimaryVisible || isSecondaryVisible ) { - SetupTouchEvents(); - CreateSelectionHandles(); if( isPrimaryVisible ) { - primary.actor.SetPosition( primaryPosition.x, - primaryPosition.y + primary.lineHeight ); + SetSelectionHandlePosition( LEFT_SELECTION_HANDLE ); + + // Sets the primary handle image according if it's pressed, flipped, etc. + SetHandleImage( LEFT_SELECTION_HANDLE ); + + SetSelectionHandleMarkerSize( primary ); } if( isSecondaryVisible ) { - secondary.actor.SetPosition( secondaryPosition.x, - secondaryPosition.y + secondary.lineHeight ); + SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE ); + + // Sets the secondary handle image according if it's pressed, flipped, etc. + SetHandleImage( RIGHT_SELECTION_HANDLE ); + + SetSelectionHandleMarkerSize( secondary ); } } - primary.actor.SetVisible( isPrimaryVisible ); - secondary.actor.SetVisible( isSecondaryVisible ); + + if( primary.actor ) + { + primary.actor.SetVisible( isPrimaryVisible ); + } + if( secondary.actor ) + { + secondary.actor.SetVisible( isSecondaryVisible ); + } CreateHighlight(); UpdateHighlight(); } else { - UnparentAndReset( primary.actor ); - UnparentAndReset( secondary.actor ); - UnparentAndReset( mHighlightMeshActor ); + if( primary.actor ) + { + primary.actor.Unparent(); + } + if( secondary.actor ) + { + secondary.actor.Unparent(); + } + if( mHighlightActor ) + { + mHighlightActor.Unparent(); + } } - if ( mActiveCopyPastePopup ) + if( mActiveCopyPastePopup ) { - // todo Swap UnparentAndReset for DeterminePositionPopup() if mCopyPastePopup.actor valid Once the issue with the labels disappearing is fixed. - UnparentAndReset( mCopyPastePopup.actor ); - if ( !mCopyPastePopup.actor ) - { - mCopyPastePopup.actor = TextSelectionPopup::New( mEnabledPopupButtons, &mTextSelectionPopupCallbackInterface ); -#ifdef DECORATOR_DEBUG - mCopyPastePopup.actor.SetName("mCopyPastePopup"); -#endif - mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER ); - mCopyPastePopup.actor.OnRelayoutSignal().Connect( this, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation - mActiveLayer.Add ( mCopyPastePopup.actor ); - } + ShowPopup(); } else { - if ( mCopyPastePopup.actor ) - { - UnparentAndReset( mCopyPastePopup.actor ); - } + if( mCopyPastePopup.actor ) + { + mCopyPastePopup.actor.HidePopup(); + mPopupSetNewPosition = true; + } } } @@ -435,41 +437,68 @@ struct Decorator::Impl : public ConnectionTracker mHighlightPosition += scrollOffset; } - void DeterminePositionPopup() + void ShowPopup() { - if ( !mActiveCopyPastePopup ) + if ( !mCopyPastePopup.actor ) { return; } - if ( mHandle[LEFT_SELECTION_HANDLE].active || mHandle[RIGHT_SELECTION_HANDLE].active ) + if( !mCopyPastePopup.actor.GetParent() ) { - float minHandleXPosition = std::min ( mHandle[LEFT_SELECTION_HANDLE].position.x, mHandle[RIGHT_SELECTION_HANDLE].position.x ); - float maxHandleXPosition = std::max ( mHandle[LEFT_SELECTION_HANDLE].position.x, mHandle[RIGHT_SELECTION_HANDLE].position.x ); + mActiveLayer.Add( mCopyPastePopup.actor ); + } - float minHandleYPosition = std::min ( mHandle[LEFT_SELECTION_HANDLE].position.y, mHandle[RIGHT_SELECTION_HANDLE].position.y ); + mCopyPastePopup.actor.RaiseAbove( mActiveLayer ); + mCopyPastePopup.actor.ShowPopup(); + } - mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) *0.5f ); - mCopyPastePopup.position.y = minHandleYPosition + mCopyPastePopup.offset; - } - else + void DeterminePositionPopup() + { + if( !mActiveCopyPastePopup ) { - mCopyPastePopup.position = Vector3( mCursor[PRIMARY_CURSOR].position.x, mCursor[PRIMARY_CURSOR].position.y -100.0f , 0.0f ); //todo 100 to be an offset Property + return; } - Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f ); + // Retrieves the popup's size after relayout. + const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f ); - GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize, AnchorPoint::CENTER, mActiveLayer, mBoundingBox ); + if( mPopupSetNewPosition ) + { + 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. - mCopyPastePopup.actor.OnRelayoutSignal().Disconnect( this, &Decorator::Impl::PopupRelayoutComplete ); DeterminePositionPopup(); } @@ -477,8 +506,9 @@ struct Decorator::Impl : public ConnectionTracker void CreateCursor( ImageActor& cursor, const Vector4& color ) { cursor = CreateSolidColorActor( color ); + cursor.SetSortModifier( DECORATION_DEPTH_INDEX ); cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one. - cursor.SetAnchorPoint( AnchorPoint::TOP_CENTER ); + cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); } // Add or Remove cursor(s) from parent @@ -486,12 +516,18 @@ 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 ) { @@ -501,6 +537,10 @@ struct Decorator::Impl : public ConnectionTracker #ifdef DECORATOR_DEBUG mPrimaryCursor.SetName( "PrimaryCursorActor" ); #endif + } + + if( !mPrimaryCursor.GetParent() ) + { mActiveLayer.Add( mPrimaryCursor ); } } @@ -513,46 +553,55 @@ struct Decorator::Impl : public ConnectionTracker #ifdef DECORATOR_DEBUG mSecondaryCursor.SetName( "SecondaryCursorActor" ); #endif + } + + if( !mSecondaryCursor.GetParent() ) + { mActiveLayer.Add( mSecondaryCursor ); } } else { - UnparentAndReset( mSecondaryCursor ); + if( mSecondaryCursor ) + { + mSecondaryCursor.Unparent(); + } } } } bool OnCursorBlinkTimerTick() { - // Cursor blinking - if ( mPrimaryCursor ) + if( !mDelayCursorBlink ) { - mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus ); + // Cursor blinking + if ( mPrimaryCursor ) + { + mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus ); + } + if ( mSecondaryCursor ) + { + mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus ); + } + + mCursorBlinkStatus = !mCursorBlinkStatus; } - if ( mSecondaryCursor ) + else { - mSecondaryCursor.SetVisible( mSecondaryCursorVisible && 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() @@ -575,23 +624,22 @@ struct Decorator::Impl : public ConnectionTracker mActiveLayer.RaiseToTop(); } + void SetSelectionHandleMarkerSize( HandleImpl& handle ) + { + if( handle.markerActor ) + { + handle.markerActor.SetSize( 0, handle.lineHeight ); + } + } + void CreateGrabHandle() { HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; if( !grabHandle.actor ) { - if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] ) - { - mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_RELEASED ); - } - if( !mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] ) - { - mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_GRAB_HANDLE_IMAGE_PRESSED ); - } - grabHandle.actor = ImageActor::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] ); + grabHandle.actor.SetSortModifier( DECORATION_DEPTH_INDEX ); grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER ); - grabHandle.actor.SetDrawMode( DrawMode::OVERLAY ); // Area that Grab handle responds to, larger than actual handle so easier to move #ifdef DECORATOR_DEBUG grabHandle.actor.SetName( "GrabHandleActor" ); @@ -614,6 +662,7 @@ struct Decorator::Impl : public ConnectionTracker 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 ); @@ -621,197 +670,382 @@ struct Decorator::Impl : public ConnectionTracker mActiveLayer.Add( grabHandle.actor ); } + + if( !grabHandle.actor.GetParent() ) + { + mActiveLayer.Add( grabHandle.actor ); + } } - void CreateSelectionHandles() + void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType ) { - HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ]; - if( !primary.actor ) + if( image ) { - if( !mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ) + handle.markerActor = ImageActor::New( image ); + handle.markerActor.SetColor( mHandleColor ); + handle.actor.Add( handle.markerActor ); + + handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT ); + + if( LEFT_SELECTION_HANDLE == handleType ) { - mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_RELEASED ); + handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT ); + handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT ); } - if( !mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] ) + else if( RIGHT_SELECTION_HANDLE == handleType ) { - mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED ); + handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT ); + handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT ); } + } + } + void CreateSelectionHandles() + { + HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ]; + if( !primary.actor ) + { primary.actor = ImageActor::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); #ifdef DECORATOR_DEBUG primary.actor.SetName("SelectionHandleOne"); #endif 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.SetSortModifier( DECORATION_DEPTH_INDEX ); + primary.actor.SetColor( mHandleColor ); 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"); #endif 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 ); - primary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION ); mTapDetector.Attach( primary.grabArea ); mPanGestureDetector.Attach( primary.grabArea ); primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched ); primary.actor.Add( primary.grabArea ); + + CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE ); + } + + if( !primary.actor.GetParent() ) + { mActiveLayer.Add( primary.actor ); } HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ]; if( !secondary.actor ) { - if( !mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ) - { - mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_RELEASED ); - } - if( !mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] ) - { - mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED] = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED ); - } - secondary.actor = ImageActor::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] ); #ifdef DECORATOR_DEBUG secondary.actor.SetName("SelectionHandleTwo"); #endif 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.SetSortModifier( DECORATION_DEPTH_INDEX ); + secondary.actor.SetColor( mHandleColor ); 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"); #endif 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 ); - secondary.grabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION ); mTapDetector.Attach( secondary.grabArea ); mPanGestureDetector.Attach( secondary.grabArea ); secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched ); secondary.actor.Add( secondary.grabArea ); + + CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE ); + } + + if( !secondary.actor.GetParent() ) + { mActiveLayer.Add( secondary.actor ); } } - void CreateHighlight() + void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position ) + { + // Get the world position of the active layer + const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition(); + + // Get the size of the UI control. + Vector2 targetSize; + mController.GetTargetSize( targetSize ); + + // The grab handle position in world coords. + position.x = parentWorldPosition.x - 0.5f * targetSize.width + handle.position.x; + position.y = parentWorldPosition.y - 0.5f * targetSize.height + handle.position.y + handle.lineHeight; + } + + void SetGrabHandlePosition() + { + // Reference to the grab handle. + HandleImpl& grabHandle = mHandle[GRAB_HANDLE]; + + // The grab handle position in world coords. + 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.lineHeight - grabHandle.size.height ) > mBoundingBox.y ) ) || + ( grabHandleWorldPosition.y + 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; + + grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ), + yLocalPosition ); // TODO : Fix for multiline. + } + + void SetSelectionHandlePosition( HandleType type ) { - if ( !mHighlightMeshActor ) + const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type; + + // Reference to the selection handle. + HandleImpl& handle = mHandle[type]; + + // Get the world coordinates of the handle position. + 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 ) + { + crossFlip = mHandleCurrentCrossed; + } + + // Does not flip if both conditions are true (double flip) + flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed ); + + // Will flip the handles vertically if the user prefers it. + bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred; + + if( crossFlip || mHandlePreviousCrossed ) + { + if( isPrimaryHandle ) + { + verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred; + } + else + { + verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred; + } + } + + // 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 ); + + // Does not flip if both conditions are true (double flip) + flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge ); + + if( flipHandle ) + { + if( !handle.horizontallyFlipped ) + { + // Change the anchor point to flip the image. + handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT ); + + handle.horizontallyFlipped = true; + } + } + else + { + if( handle.horizontallyFlipped ) + { + // Reset the anchor point. + handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT ); + + handle.horizontallyFlipped = false; + } + } + + // Whether to flip the handle vertically. + handle.verticallyFlipped = ( verticallyFlippedPreferred && + ( ( handleWorldPosition.y - handle.lineHeight - handle.size.height ) > mBoundingBox.y ) ) || + ( handleWorldPosition.y + handle.size.height > mBoundingBox.w ); + + // 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; + + handle.actor.SetPosition( handle.position.x, + yLocalPosition ); // TODO : Fix for multiline. + } + + void SetHandleImage( HandleType type ) + { + HandleImpl& handle = mHandle[type]; + + 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; + } + + // 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 ); + + handle.actor.SetImage( mHandleImages[type][imageType] ); + } + + if( HANDLE_TYPE_COUNT != markerType ) { - mHighlightMaterial = Material::New( "HighlightMaterial" ); - mHighlightMaterial.SetDiffuseColor( mHighlightColor ); + 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] ); + } + } - mHighlightMeshData.SetMaterial( mHighlightMaterial ); - mHighlightMeshData.SetHasNormals( true ); + // Whether to flip the handle vertically. + if( handle.actor ) + { + handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS ); + } + } - mHighlightMesh = Mesh::New( mHighlightMeshData ); + void CreateHighlight() + { + if( !mHighlightActor ) + { + mHighlightActor = Actor::New(); - mHighlightMeshActor = MeshActor::New( mHighlightMesh ); #ifdef DECORATOR_DEBUG - mHighlightMeshActor.SetName( "HighlightMeshActor" ); + mHighlightActor.SetName( "HighlightActor" ); #endif - mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); - - // Add the highlight box telling the controller it needs clipping. - mController.AddDecoration( mHighlightMeshActor, true ); + mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + mHighlightActor.SetSize( 1.0f, 1.0f ); + mHighlightActor.SetColor( mHighlightColor ); + mHighlightActor.SetColorMode( USE_OWN_COLOR ); } - mHighlightMeshActor.SetPosition( mHighlightPosition.x, mHighlightPosition.y, DISPLAYED_HIGHLIGHT_Z_OFFSET ); + // Add the highlight box telling the controller it needs clipping. + mController.AddDecoration( mHighlightActor, true ); } void UpdateHighlight() { - // 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() ) + if ( mHighlightActor ) { - MeshData::VertexContainer vertices; - Dali::MeshData::FaceIndices faceIndices; + if( !mHighlightQuadList.empty() ) + { + Vector< Vector2 > vertices; + Vector< unsigned int> indices; + Vector2 vertex; - std::vector::iterator iter = mHighlightQuadList.begin(); - std::vector::iterator endIter = mHighlightQuadList.end(); + 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 ) + { + 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 ); + } - 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 ); + if( mQuadVertices ) + { + mQuadVertices.SetSize( vertices.Size() ); + } + else + { + mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() ); + } + + if( mQuadIndices ) + { + mQuadIndices.SetSize( indices.Size() ); + } + else + { + mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() ); + } + + mQuadVertices.SetData( &vertices[ 0 ] ); + mQuadIndices.SetData( &indices[ 0 ] ); + + if( !mQuadGeometry ) + { + mQuadGeometry = Geometry::New(); + mQuadGeometry.AddVertexBuffer( mQuadVertices ); + } + mQuadGeometry.SetIndexBuffer( mQuadIndices ); + + if( !mHighlightRenderer ) + { + mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial ); + mHighlightActor.AddRenderer( mHighlightRenderer ); + } } - BoneContainer bones(0); // passed empty as bones not required - mHighlightMeshData.SetData( vertices, faceIndices, bones, mHighlightMaterial ); - mHighlightMesh.UpdateMeshData( mHighlightMeshData ); + mHighlightActor.SetPosition( mHighlightPosition.x, + mHighlightPosition.y ); + + mHighlightQuadList.clear(); + + if( mHighlightRenderer ) + { + mHighlightRenderer.SetDepthIndex( mTextDepth - 2u ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u. + } } } @@ -860,12 +1094,16 @@ struct Decorator::Impl : public ConnectionTracker StopScrollTimer(); mController.DecorationEvent( type, HANDLE_PRESSED, x, y ); } + + mHandlePanning = true; } else if( Gesture::Finished == gesture.state || Gesture::Cancelled == gesture.state ) { - if( mScrollTimer && mScrollTimer.IsRunning() ) + if( mScrollTimer && + ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) ) { + mNotifyEndOfScroll = false; mHandleScrolling = HANDLE_TYPE_COUNT; StopScrollTimer(); mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y ); @@ -875,22 +1113,10 @@ struct Decorator::Impl : public ConnectionTracker mController.DecorationEvent( type, HANDLE_RELEASED, x, y ); } - if( GRAB_HANDLE == type ) - { - handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] ); - } - else - { - HandleType selectionHandleType = type; - - if( mSwapSelectionHandles != handle.flipped ) - { - selectionHandleType = ( LEFT_SELECTION_HANDLE == type ) ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE; - } - - handle.actor.SetImage( mHandleImages[selectionHandleType][HANDLE_IMAGE_RELEASED] ); - } + handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] ); handle.pressed = false; + + mHandlePanning = false; } } @@ -925,21 +1151,14 @@ struct Decorator::Impl : public ConnectionTracker if( TouchPoint::Down == point.state ) { mHandle[GRAB_HANDLE].pressed = true; - Image imagePressed = mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_PRESSED]; - if( imagePressed ) - { - mHandle[GRAB_HANDLE].actor.SetImage( imagePressed ); - } } - else if( TouchPoint::Up == point.state ) + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) { mHandle[GRAB_HANDLE].pressed = false; - Image imageReleased = mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED]; - if( imageReleased ) - { - mHandle[GRAB_HANDLE].actor.SetImage( imageReleased ); - } } + + SetHandleImage( GRAB_HANDLE ); } // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area @@ -954,25 +1173,19 @@ struct Decorator::Impl : public ConnectionTracker { const TouchPoint& point = event.GetPoint(0); - const bool flip = mSwapSelectionHandles != mHandle[LEFT_SELECTION_HANDLE].flipped; if( TouchPoint::Down == point.state ) { mHandle[LEFT_SELECTION_HANDLE].pressed = true; - Image imagePressed = mHandleImages[flip ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED]; - if( imagePressed ) - { - mHandle[LEFT_SELECTION_HANDLE].actor.SetImage( imagePressed ); - } } - else if( TouchPoint::Up == point.state ) + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) { mHandle[LEFT_SELECTION_HANDLE].pressed = false; - Image imageReleased = mHandleImages[flip ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED]; - if( imageReleased ) - { - mHandle[LEFT_SELECTION_HANDLE].actor.SetImage( imageReleased ); - } + mHandlePreviousCrossed = mHandleCurrentCrossed; + mHandlePanning = false; } + + SetHandleImage( LEFT_SELECTION_HANDLE ); } // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area @@ -987,25 +1200,19 @@ struct Decorator::Impl : public ConnectionTracker { const TouchPoint& point = event.GetPoint(0); - const bool flip = mSwapSelectionHandles != mHandle[RIGHT_SELECTION_HANDLE].flipped; if( TouchPoint::Down == point.state ) { - Image imagePressed = mHandleImages[flip ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_PRESSED]; mHandle[RIGHT_SELECTION_HANDLE].pressed = true; - if( imagePressed ) - { - mHandle[RIGHT_SELECTION_HANDLE].actor.SetImage( imagePressed ); - } } - else if( TouchPoint::Up == point.state ) + else if( ( TouchPoint::Up == point.state ) || + ( TouchPoint::Interrupted == point.state ) ) { - Image imageReleased = mHandleImages[flip ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED]; mHandle[RIGHT_SELECTION_HANDLE].pressed = false; - if( imageReleased ) - { - mHandle[RIGHT_SELECTION_HANDLE].actor.SetImage( imageReleased ); - } + mHandlePreviousCrossed = mHandleCurrentCrossed; + mHandlePanning = false; } + + SetHandleImage( RIGHT_SELECTION_HANDLE ); } // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area @@ -1016,26 +1223,23 @@ struct Decorator::Impl : public ConnectionTracker float AlternatePopUpPositionRelativeToCursor() { - float alternativePosition=0.0f;; + float alternativePosition = 0.0f; - if ( mPrimaryCursor ) // Secondary cursor not used for paste - { - Cursor cursor = PRIMARY_CURSOR; - alternativePosition = mCursor[cursor].position.y; - } + const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ); - const float popupHeight = 120.0f; // todo Set as a MaxSize Property in Control or retrieve from CopyPastePopup class. + 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( mHandle[GRAB_HANDLE].active ) + if( primaryHandle.active || secondaryHandle.active ) { - // If grab handle enabled then position pop-up below the grab handle. - const Vector2 grabHandleSize( 59.0f, 56.0f ); // todo - const float BOTTOM_HANDLE_BOTTOM_OFFSET = 1.5; //todo Should be a property - alternativePosition += grabHandleSize.height + popupHeight + BOTTOM_HANDLE_BOTTOM_OFFSET ; + 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 += popupHeight; + alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y; } return alternativePosition; @@ -1043,7 +1247,7 @@ struct Decorator::Impl : public ConnectionTracker void PopUpLeavesVerticalBoundary( PropertyNotification& source ) { - float alternativeYPosition=0.0f; + float alternativeYPosition = 0.0f; // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted // if can't be positioned above, then position below row. alternativeYPosition = AlternatePopUpPositionRelativeToCursor(); @@ -1051,38 +1255,28 @@ struct Decorator::Impl : public ConnectionTracker 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 - Vector4 worldCoordinatesBoundingBox; - LocalToWorldCoordinatesBoundingBox( mBoundingBox, worldCoordinatesBoundingBox ); - - float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT); + const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ); PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y, - OutsideCondition( worldCoordinatesBoundingBox.y + popupHeight * 0.5f, - worldCoordinatesBoundingBox.w - popupHeight * 0.5f ) ); + OutsideCondition( mBoundingBox.y + popupHeight * 0.5f, + mBoundingBox.w - popupHeight * 0.5f ) ); verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary ); } - void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, Vector3& popupSize, Vector3 anchorPoint, Actor& parent, Rect& boundingBox ) + 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 - Vector3 parentAnchorPoint = parent.GetCurrentAnchorPoint(); - Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize()*parentAnchorPoint; - Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position - Vector3 popupDistanceFromAnchorPoint = popupSize*anchorPoint; - - // Bounding rectangle is supplied as screen coordinates, bounding will be done in world coordinates. - Vector4 boundingRectangleWorld; - LocalToWorldCoordinatesBoundingBox( boundingBox, boundingRectangleWorld ); + 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; @@ -1090,13 +1284,13 @@ struct Decorator::Impl : public ConnectionTracker { xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x ); } - else if ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z ) + 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 ) + if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y ) { requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor(); } @@ -1104,32 +1298,16 @@ struct Decorator::Impl : public ConnectionTracker requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds; // Prevent pixel mis-alignment by rounding down. - requiredPopupPosition.x = static_cast( requiredPopupPosition.x ); - requiredPopupPosition.y = static_cast( requiredPopupPosition.y ); - + requiredPopupPosition.x = floor( requiredPopupPosition.x ); + requiredPopupPosition.y = floor( requiredPopupPosition.y ); } - void FlipSelectionHandleImages() + void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image ) { - SetupTouchEvents(); - - CreateSelectionHandles(); - - HandleImpl& leftHandle = mHandle[LEFT_SELECTION_HANDLE]; - HandleImpl& rightHandle = mHandle[RIGHT_SELECTION_HANDLE]; - - const HandleImageType leftImageType = leftHandle.pressed ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED; - const HandleImageType rightImageType = rightHandle.pressed ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED; - const bool leftFlipped = mSwapSelectionHandles != leftHandle.flipped; - const bool rightFlipped = mSwapSelectionHandles != rightHandle.flipped; + HandleImpl& handle = mHandle[handleType]; + handle.size = Size( image.GetWidth(), image.GetHeight() ); - leftHandle.actor.SetImage( leftFlipped ? mHandleImages[RIGHT_SELECTION_HANDLE][leftImageType] : mHandleImages[LEFT_SELECTION_HANDLE][leftImageType] ); - - leftHandle.actor.SetAnchorPoint( leftFlipped ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT ); - - rightHandle.actor.SetImage( rightFlipped ? mHandleImages[LEFT_SELECTION_HANDLE][rightImageType] : mHandleImages[RIGHT_SELECTION_HANDLE][rightImageType] ); - - rightHandle.actor.SetAnchorPoint( rightFlipped ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT ); + mHandleImages[handleType][handleImageType] = image; } void SetScrollThreshold( float threshold ) @@ -1153,6 +1331,16 @@ struct Decorator::Impl : public ConnectionTracker 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. * @@ -1211,41 +1399,55 @@ struct Decorator::Impl : public ConnectionTracker Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else. ImageActor mPrimaryCursor; ImageActor mSecondaryCursor; - MeshActor mHighlightMeshActor; ///< Mesh Actor to display highlight + Actor mHighlightActor; ///< Actor to display highlight + Renderer mHighlightRenderer; + Material mHighlightMaterial; ///< Material 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]; - Image mCursorImage; - Mesh mHighlightMesh; ///< Mesh for highlight - MeshData mHighlightMeshData; ///< Mesh Data for highlight - Material mHighlightMaterial; ///< Material used for highlight + 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 - Vector2 mHighlightPosition; ///< The position of the highlight actor. - Rect mBoundingBox; + Vector4 mBoundingBox; ///< The bounding box in world coords. Vector4 mHighlightColor; ///< Color of the highlight + Vector2 mHighlightPosition; ///< The position of the highlight actor. 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. - unsigned int mScrollInterval; ///< Time in milliseconds of a scroll interval. - - bool mActiveCopyPastePopup : 1; - 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. + 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, @@ -1257,12 +1459,12 @@ DecoratorPtr Decorator::New( ControllerInterface& controller, void Decorator::SetBoundingBox( const Rect& boundingBox ) { - mImpl->mBoundingBox = boundingBox; + LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox ); } -const Rect& Decorator::GetBoundingBox() const +void Decorator::GetBoundingBox( Rect& boundingBox ) const { - return mImpl->mBoundingBox; + WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox ); } void Decorator::Relayout( const Vector2& size ) @@ -1308,7 +1510,7 @@ const Vector2& Decorator::GetPosition( Cursor cursor ) const 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; } @@ -1338,6 +1540,14 @@ 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 ) @@ -1360,11 +1570,41 @@ float Decorator::GetCursorBlinkDuration() const return mImpl->mCursorBlinkDuration; } +void Decorator::SetCursorWidth( int width ) +{ + mImpl->mCursorWidth = static_cast( width ); +} + +int Decorator::GetCursorWidth() const +{ + return static_cast( mImpl->mCursorWidth ); +} + /** Handles **/ void Decorator::SetHandleActive( HandleType handleType, bool active ) { 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]; + ImageActor imageActor = mImpl->mHandle[handleType].actor; + if( imageReleased && imageActor ) + { + imageActor.SetImage( imageReleased ); + } + } + } bool Decorator::IsHandleActive( HandleType handleType ) const @@ -1374,7 +1614,7 @@ bool Decorator::IsHandleActive( HandleType handleType ) const void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image ) { - mImpl->mHandleImages[handleType][handleImageType] = image; + mImpl->SetHandleImage( handleType, handleImageType, image ); } Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const @@ -1382,6 +1622,16 @@ Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType ha return mImpl->mHandleImages[handleType][handleImageType]; } +void Decorator::SetHandleColor( const Vector4& color ) +{ + mImpl->mHandleColor = color; +} + +const Vector4& Decorator::GetHandleColor() const +{ + return mImpl->mHandleColor; +} + void Decorator::SetPosition( HandleType handleType, float x, float y, float height ) { // Adjust grab handle displacement @@ -1409,11 +1659,26 @@ const Vector2& Decorator::GetPosition( HandleType handleType ) const return mImpl->mHandle[handleType].position; } -void Decorator::SwapSelectionHandlesEnabled( bool enable ) +void Decorator::FlipHandleVertically( HandleType handleType, bool flip ) { - mImpl->mSwapSelectionHandles = enable; + mImpl->mHandle[handleType].verticallyFlippedPreferred = flip; +} - mImpl->FlipSelectionHandleImages(); +bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const +{ + return mImpl->mHandle[handleType].verticallyFlippedPreferred; +} + +void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable ) +{ + mImpl->mFlipSelectionHandlesOnCross = enable; +} + +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 ) @@ -1437,6 +1702,11 @@ const Vector4& Decorator::GetHighlightColor() const return mImpl->mHighlightColor; } +void Decorator::SetTextDepth( int textDepth ) +{ + mImpl->mTextDepth = textDepth; +} + void Decorator::SetPopupActive( bool active ) { mImpl->mActiveCopyPastePopup = active; @@ -1449,7 +1719,19 @@ bool Decorator::IsPopupActive() const void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask ) { - mImpl->mEnabledPopupButtons = 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() @@ -1479,6 +1761,11 @@ float Decorator::GetScrollSpeed() const return mImpl->GetScrollSpeed(); } +void Decorator::NotifyEndOfScroll() +{ + mImpl->NotifyEndOfScroll(); +} + Decorator::~Decorator() { delete mImpl;