2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
22 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/adaptor-framework/timer.h>
25 #include <dali/public-api/actors/layer.h>
26 #include <dali/public-api/common/stage.h>
27 #include <dali/public-api/events/touch-event.h>
28 #include <dali/public-api/events/pan-gesture.h>
29 #include <dali/public-api/images/resource-image.h>
30 #include <dali/public-api/object/property-notification.h>
32 #include <dali/devel-api/rendering/geometry.h>
33 #include <dali/devel-api/rendering/renderer.h>
36 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
37 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
38 #include <dali-toolkit/internal/controls/image-view/image-view-impl.h>
41 #define DECORATOR_DEBUG
45 #define MAKE_SHADER(A)#A
49 const char* VERTEX_SHADER = MAKE_SHADER(
50 attribute mediump vec2 aPosition;
51 uniform mediump mat4 uMvpMatrix;
52 uniform mediump vec3 uSize;
56 mediump vec4 position = vec4( aPosition, 0.0, 1.0 );
57 position.xyz *= uSize;
58 gl_Position = uMvpMatrix * position;
62 const char* FRAGMENT_SHADER = MAKE_SHADER(
63 uniform lowp vec4 uColor;
67 gl_FragColor = uColor;
78 #ifdef DECORATOR_DEBUG
79 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
89 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
90 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
92 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.
94 const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f );
96 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
97 const float TO_MILLISECONDS = 1000.f;
98 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
100 const unsigned int SCROLL_TICK_INTERVAL = 50u;
102 const float SCROLL_THRESHOLD = 10.f;
103 const float SCROLL_SPEED = 300.f;
104 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
106 const float CURSOR_WIDTH = 1.f;
109 * structure to hold coordinates of each quad, which will make up the mesh.
111 struct QuadCoordinates
114 * Default constructor
122 * @param[in] x1 left co-ordinate
123 * @param[in] y1 top co-ordinate
124 * @param[in] x2 right co-ordinate
125 * @param[in] y2 bottom co-ordinate
127 QuadCoordinates(float x1, float y1, float x2, float y2)
133 Dali::Vector2 min; ///< top-left (minimum) position of quad
134 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
137 typedef std::vector<QuadCoordinates> QuadContainer;
140 * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
141 * @param[in] boundingRectangle local bounding
142 * @param[out] Vector4 World coordinate bounding Box.
144 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
146 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
147 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
149 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
150 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
152 boundingBox = Dali::Vector4( originX,
154 originX + boundingRectangle.width,
155 originY + boundingRectangle.height );
158 void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect<int>& boundingRectangle )
160 // Convert to local coordinates and store as a Dali::Rect.
161 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
163 boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width;
164 boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height;
165 boundingRectangle.width = boundingBox.z - boundingBox.x;
166 boundingRectangle.height = boundingBox.w - boundingBox.y;
169 } // end of namespace
180 struct Decorator::Impl : public ConnectionTracker
194 : color( Dali::Color::BLACK ),
196 cursorHeight( 0.0f ),
213 grabDisplacementX( 0.f ),
214 grabDisplacementY( 0.f ),
218 verticallyFlippedPreferred( false ),
219 horizontallyFlipped( false ),
220 verticallyFlipped( false )
226 ImageView markerActor;
230 float lineHeight; ///< Not the handle height
231 float grabDisplacementX;
232 float grabDisplacementY;
236 bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped.
237 bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped.
238 bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped.
248 TextSelectionPopup actor;
252 Impl( ControllerInterface& controller,
253 TextSelectionPopupCallbackInterface& callbackInterface )
254 : mController( controller ),
255 mEnabledPopupButtons( TextSelectionPopup::NONE ),
256 mTextSelectionPopupCallbackInterface( callbackInterface ),
257 mHandleColor( HANDLE_COLOR ),
259 mHighlightColor( LIGHT_BLUE ),
260 mHighlightPosition( Vector2::ZERO ),
261 mActiveCursor( ACTIVE_CURSOR_NONE ),
262 mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
263 mCursorBlinkDuration( 0.0f ),
264 mCursorWidth( CURSOR_WIDTH ),
265 mHandleScrolling( HANDLE_TYPE_COUNT ),
266 mScrollDirection( SCROLL_NONE ),
267 mScrollThreshold( SCROLL_THRESHOLD ),
268 mScrollSpeed( SCROLL_SPEED ),
269 mScrollDistance( SCROLL_DISTANCE ),
271 mActiveCopyPastePopup( false ),
272 mPopupSetNewPosition( true ),
273 mCursorBlinkStatus( true ),
274 mDelayCursorBlink( false ),
275 mPrimaryCursorVisible( false ),
276 mSecondaryCursorVisible( false ),
277 mFlipSelectionHandlesOnCross( false ),
278 mFlipLeftSelectionHandleDirection( false ),
279 mFlipRightSelectionHandleDirection( false ),
280 mHandlePanning( false ),
281 mHandleCurrentCrossed( false ),
282 mHandlePreviousCrossed( false ),
283 mNotifyEndOfScroll( false )
285 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
286 mHighlightShader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER );
291 * Relayout of the decorations owned by the decorator.
292 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
294 void Relayout( const Vector2& size )
298 // TODO - Remove this if nothing is active
301 // Show or hide the cursors
306 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
307 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
308 if( mPrimaryCursorVisible )
310 mPrimaryCursor.SetPosition( cursor.position.x,
312 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
314 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
316 if( mSecondaryCursor )
318 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
319 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
320 if( mSecondaryCursorVisible )
322 mSecondaryCursor.SetPosition( cursor.position.x,
324 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
326 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
329 // Show or hide the grab handle
330 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
331 bool newGrabHandlePosition = false;
332 if( grabHandle.active )
334 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
340 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
341 SetGrabHandlePosition();
343 // Sets the grab handle image according if it's pressed, flipped, etc.
344 SetHandleImage( GRAB_HANDLE );
346 newGrabHandlePosition = true;
349 if( grabHandle.actor )
351 grabHandle.actor.SetVisible( isVisible );
354 else if( grabHandle.actor )
356 grabHandle.actor.Unparent();
359 // Show or hide the selection handles/highlight
360 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
361 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
362 bool newPrimaryHandlePosition = false;
363 bool newSecondaryHandlePosition = false;
364 if( primary.active || secondary.active )
366 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
367 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
369 if( isPrimaryVisible || isSecondaryVisible )
371 CreateSelectionHandles();
373 if( isPrimaryVisible )
375 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
377 // Sets the primary handle image according if it's pressed, flipped, etc.
378 SetHandleImage( LEFT_SELECTION_HANDLE );
380 SetSelectionHandleMarkerSize( primary );
382 newPrimaryHandlePosition = true;
385 if( isSecondaryVisible )
387 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
389 // Sets the secondary handle image according if it's pressed, flipped, etc.
390 SetHandleImage( RIGHT_SELECTION_HANDLE );
392 SetSelectionHandleMarkerSize( secondary );
394 newSecondaryHandlePosition = true;
400 primary.actor.SetVisible( isPrimaryVisible );
402 if( secondary.actor )
404 secondary.actor.SetVisible( isSecondaryVisible );
414 primary.actor.Unparent();
416 if( secondary.actor )
418 secondary.actor.Unparent();
420 if( mHighlightActor )
422 mHighlightActor.Unparent();
426 if( newGrabHandlePosition ||
427 newPrimaryHandlePosition ||
428 newSecondaryHandlePosition )
430 // Setup property notifications to find whether the handles leave the boundaries of the current display.
431 SetupActiveLayerPropertyNotifications();
434 if( mActiveCopyPastePopup )
437 mPopupSetNewPosition = true;
441 if( mCopyPastePopup.actor )
443 mCopyPastePopup.actor.HidePopup();
444 mPopupSetNewPosition = true;
449 void UpdatePositions( const Vector2& scrollOffset )
451 mCursor[PRIMARY_CURSOR].position += scrollOffset;
452 mCursor[SECONDARY_CURSOR].position += scrollOffset;
453 mHandle[ GRAB_HANDLE ].position += scrollOffset;
454 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
455 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
456 mHighlightPosition += scrollOffset;
461 if ( !mCopyPastePopup.actor )
466 if( !mCopyPastePopup.actor.GetParent() )
468 mActiveLayer.Add( mCopyPastePopup.actor );
471 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
472 mCopyPastePopup.actor.ShowPopup();
475 void DeterminePositionPopup()
477 if( !mActiveCopyPastePopup )
482 // Retrieves the popup's size after relayout.
483 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
485 if( mPopupSetNewPosition )
487 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
488 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
489 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
490 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
492 if( primaryHandle.active || secondaryHandle.active )
494 // Calculates the popup's position if selection handles are active.
495 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
496 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
497 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
499 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
500 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
504 // Calculates the popup's position if the grab handle is active.
505 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
509 // Checks if there is enough space above the text control. If not it places the popup under it.
510 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
512 SetUpPopupPositionNotifications();
514 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
515 mPopupSetNewPosition = false;
518 void PopupRelayoutComplete( Actor actor )
520 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
522 DeterminePositionPopup();
525 void CreateCursor( Control& cursor, const Vector4& color )
527 cursor = Control::New();
528 cursor.SetBackgroundColor( color );
529 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT );
530 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
533 // Add or Remove cursor(s) from parent
536 if( mActiveCursor == ACTIVE_CURSOR_NONE )
540 mPrimaryCursor.Unparent();
542 if( mSecondaryCursor )
544 mSecondaryCursor.Unparent();
549 // Create Primary and or Secondary Cursor(s) if active and add to parent
550 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
551 mActiveCursor == ACTIVE_CURSOR_BOTH )
553 if ( !mPrimaryCursor )
555 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
556 #ifdef DECORATOR_DEBUG
557 mPrimaryCursor.SetName( "PrimaryCursorActor" );
561 if( !mPrimaryCursor.GetParent() )
563 mActiveLayer.Add( mPrimaryCursor );
567 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
569 if ( !mSecondaryCursor )
571 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
572 #ifdef DECORATOR_DEBUG
573 mSecondaryCursor.SetName( "SecondaryCursorActor" );
577 if( !mSecondaryCursor.GetParent() )
579 mActiveLayer.Add( mSecondaryCursor );
584 if( mSecondaryCursor )
586 mSecondaryCursor.Unparent();
592 bool OnCursorBlinkTimerTick()
594 if( !mDelayCursorBlink )
597 if ( mPrimaryCursor )
599 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
601 if ( mSecondaryCursor )
603 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
606 mCursorBlinkStatus = !mCursorBlinkStatus;
611 mDelayCursorBlink = false;
617 void SetupTouchEvents()
619 mTapDetector = TapGestureDetector::New();
620 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
622 mPanGestureDetector = PanGestureDetector::New();
623 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
626 void CreateActiveLayer()
630 mActiveLayer = Layer::New();
631 #ifdef DECORATOR_DEBUG
632 mActiveLayer.SetName ( "ActiveLayerActor" );
635 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
636 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
638 // Add the active layer telling the controller it doesn't need clipping.
639 mController.AddDecoration( mActiveLayer, false );
642 mActiveLayer.RaiseToTop();
645 void SetSelectionHandleMarkerSize( HandleImpl& handle )
647 if( handle.markerActor )
649 handle.markerActor.SetSize( 0, handle.lineHeight );
653 void CreateGrabHandle()
655 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
656 if( !grabHandle.actor )
658 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
660 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
661 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
662 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
664 // Area that Grab handle responds to, larger than actual handle so easier to move
665 #ifdef DECORATOR_DEBUG
666 grabHandle.actor.SetName( "GrabHandleActor" );
667 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
669 grabHandle.grabArea = Control::New();
670 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
671 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
672 grabHandle.grabArea.SetName( "GrabArea" );
676 grabHandle.grabArea = Actor::New();
677 grabHandle.grabArea.SetName( "GrabArea" );
680 grabHandle.grabArea = Actor::New();
683 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
684 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
685 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
686 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
687 grabHandle.actor.Add( grabHandle.grabArea );
688 grabHandle.actor.SetColor( mHandleColor );
690 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
691 mTapDetector.Attach( grabHandle.grabArea );
692 mPanGestureDetector.Attach( grabHandle.grabArea );
694 mActiveLayer.Add( grabHandle.actor );
698 if( grabHandle.actor && !grabHandle.actor.GetParent() )
700 mActiveLayer.Add( grabHandle.actor );
704 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
708 handle.markerActor = ImageView::New( image );
709 handle.markerActor.SetColor( mHandleColor );
710 handle.actor.Add( handle.markerActor );
712 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
714 if( LEFT_SELECTION_HANDLE == handleType )
716 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
717 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
719 else if( RIGHT_SELECTION_HANDLE == handleType )
721 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
722 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
727 void CreateSelectionHandles()
729 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
732 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
734 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
735 #ifdef DECORATOR_DEBUG
736 primary.actor.SetName("SelectionHandleOne");
738 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
739 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
740 primary.actor.SetColor( mHandleColor );
742 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
743 #ifdef DECORATOR_DEBUG
744 primary.grabArea.SetName("SelectionHandleOneGrabArea");
746 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
747 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
748 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
749 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
751 mTapDetector.Attach( primary.grabArea );
752 mPanGestureDetector.Attach( primary.grabArea );
753 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
755 primary.actor.Add( primary.grabArea );
757 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
761 if( primary.actor && !primary.actor.GetParent() )
763 mActiveLayer.Add( primary.actor );
766 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
767 if( !secondary.actor )
769 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
771 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
772 #ifdef DECORATOR_DEBUG
773 secondary.actor.SetName("SelectionHandleTwo");
775 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
776 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
777 secondary.actor.SetColor( mHandleColor );
779 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
780 #ifdef DECORATOR_DEBUG
781 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
783 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
784 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
785 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
786 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
788 mTapDetector.Attach( secondary.grabArea );
789 mPanGestureDetector.Attach( secondary.grabArea );
790 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
792 secondary.actor.Add( secondary.grabArea );
794 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
798 if( secondary.actor && !secondary.actor.GetParent() )
800 mActiveLayer.Add( secondary.actor );
804 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
806 // Gets the world position of the active layer. The active layer is where the handles are added.
807 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
809 // The grab handle position in world coords.
810 // The active layer's world position is the center of the active layer. The origin of the
811 // coord system of the handles is the top left of the active layer.
812 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
813 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
816 void SetGrabHandlePosition()
818 // Reference to the grab handle.
819 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
821 // Transforms the handle position into world coordinates.
822 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
823 // as it's transforming the handle's position set by the text-controller and not
824 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
825 // retrieves the position of the center of the actor but the handle's position set
826 // by the text controller is not the center of the actor.
827 Vector2 grabHandleWorldPosition;
828 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
830 // Check if the grab handle exceeds the boundaries of the decoration box.
831 // At the moment only the height is checked for the grab handle.
833 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
834 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
835 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
837 // The grab handle 'y' position in local coords.
838 // If the grab handle exceeds the bottom of the decoration box,
839 // set the 'y' position to the top of the line.
840 // The SetGrabHandleImage() method will change the orientation.
841 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
843 if( grabHandle.actor )
845 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
846 yLocalPosition ); // TODO : Fix for multiline.
850 void SetSelectionHandlePosition( HandleType type )
852 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
854 // Reference to the selection handle.
855 HandleImpl& handle = mHandle[type];
857 // Transforms the handle position into world coordinates.
858 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
859 // as it's transforming the handle's position set by the text-controller and not
860 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
861 // retrieves the position of the center of the actor but the handle's position set
862 // by the text controller is not the center of the actor.
863 Vector2 handleWorldPosition;
864 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
866 // Whether to flip the handle (horizontally).
867 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
869 // Whether to flip the handles if they are crossed.
870 bool crossFlip = false;
871 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
873 crossFlip = mHandleCurrentCrossed;
876 // Does not flip if both conditions are true (double flip)
877 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
879 // Will flip the handles vertically if the user prefers it.
880 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
882 if( crossFlip || mHandlePreviousCrossed )
884 if( isPrimaryHandle )
886 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
890 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
894 // Check if the selection handle exceeds the boundaries of the decoration box.
895 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
896 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
898 // Does not flip if both conditions are true (double flip)
899 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
903 if( handle.actor && !handle.horizontallyFlipped )
905 // Change the anchor point to flip the image.
906 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
908 handle.horizontallyFlipped = true;
913 if( handle.actor && handle.horizontallyFlipped )
915 // Reset the anchor point.
916 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
918 handle.horizontallyFlipped = false;
922 // Whether to flip the handle vertically.
923 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
924 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
925 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
927 // The primary selection handle 'y' position in local coords.
928 // If the handle exceeds the bottom of the decoration box,
929 // set the 'y' position to the top of the line.
930 // The SetHandleImage() method will change the orientation.
931 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
935 handle.actor.SetPosition( handle.position.x,
936 yLocalPosition ); // TODO : Fix for multiline.
940 void SetHandleImage( HandleType type )
942 HandleImpl& handle = mHandle[type];
944 HandleType markerType = HANDLE_TYPE_COUNT;
945 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
946 if( LEFT_SELECTION_HANDLE == type )
948 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
949 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
951 else if( RIGHT_SELECTION_HANDLE == type )
953 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
954 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
957 // Chooses between the released or pressed image. It checks whether the pressed image exists.
960 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
962 handle.actor.SetImage( mHandleImages[type][imageType] );
965 if( HANDLE_TYPE_COUNT != markerType )
967 if( handle.markerActor )
969 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
970 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
974 // Whether to flip the handle vertically.
977 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
981 void CreateHighlight()
983 if( !mHighlightActor )
985 mHighlightActor = Actor::New();
987 #ifdef DECORATOR_DEBUG
988 mHighlightActor.SetName( "HighlightActor" );
990 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
991 mHighlightActor.SetSize( 1.0f, 1.0f );
992 mHighlightActor.SetColor( mHighlightColor );
993 mHighlightActor.SetColorMode( USE_OWN_COLOR );
996 // Add the highlight box telling the controller it needs clipping.
997 mController.AddDecoration( mHighlightActor, true );
1000 void UpdateHighlight()
1002 if ( mHighlightActor )
1004 if( !mHighlightQuadList.empty() )
1006 Vector< Vector2 > vertices;
1007 Vector< unsigned short> indices;
1010 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1011 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1013 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1015 QuadCoordinates& quad = *iter;
1018 vertex.x = quad.min.x;
1019 vertex.y = quad.min.y;
1020 vertices.PushBack( vertex );
1023 vertex.x = quad.max.x;
1024 vertex.y = quad.min.y;
1025 vertices.PushBack( vertex );
1027 // bottom-left (v+2)
1028 vertex.x = quad.min.x;
1029 vertex.y = quad.max.y;
1030 vertices.PushBack( vertex );
1032 // bottom-right (v+3)
1033 vertex.x = quad.max.x;
1034 vertex.y = quad.max.y;
1035 vertices.PushBack( vertex );
1037 // triangle A (3, 1, 0)
1038 indices.PushBack( v + 3 );
1039 indices.PushBack( v + 1 );
1040 indices.PushBack( v );
1042 // triangle B (0, 2, 3)
1043 indices.PushBack( v );
1044 indices.PushBack( v + 2 );
1045 indices.PushBack( v + 3 );
1048 if( ! mQuadVertices )
1050 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1053 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1055 if( !mQuadGeometry )
1057 mQuadGeometry = Geometry::New();
1058 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1060 mQuadGeometry.SetIndexBuffer( &indices[ 0 ], indices.Size() );
1062 if( !mHighlightRenderer )
1064 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightShader );
1065 mHighlightActor.AddRenderer( mHighlightRenderer );
1069 mHighlightActor.SetPosition( mHighlightPosition.x,
1070 mHighlightPosition.y );
1072 mHighlightQuadList.clear();
1074 if( mHighlightRenderer )
1076 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1081 void OnTap( Actor actor, const TapGesture& tap )
1083 if( actor == mHandle[GRAB_HANDLE].actor )
1089 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1091 if( Gesture::Started == gesture.state )
1093 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1096 handle.grabDisplacementX += gesture.displacement.x;
1097 handle.grabDisplacementY += gesture.displacement.y;
1099 const float x = handle.position.x + handle.grabDisplacementX;
1100 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1102 if( Gesture::Started == gesture.state ||
1103 Gesture::Continuing == gesture.state )
1106 mController.GetTargetSize( targetSize );
1108 if( x < mScrollThreshold )
1110 mScrollDirection = SCROLL_RIGHT;
1111 mHandleScrolling = type;
1114 else if( x > targetSize.width - mScrollThreshold )
1116 mScrollDirection = SCROLL_LEFT;
1117 mHandleScrolling = type;
1122 mHandleScrolling = HANDLE_TYPE_COUNT;
1124 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1127 mHandlePanning = true;
1129 else if( Gesture::Finished == gesture.state ||
1130 Gesture::Cancelled == gesture.state )
1133 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1135 mNotifyEndOfScroll = false;
1136 mHandleScrolling = HANDLE_TYPE_COUNT;
1138 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1142 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1147 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1149 handle.pressed = false;
1151 mHandlePanning = false;
1155 void OnPan( Actor actor, const PanGesture& gesture )
1157 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1158 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1159 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1161 if( actor == grabHandle.grabArea )
1163 DoPan( grabHandle, GRAB_HANDLE, gesture );
1165 else if( actor == primarySelectionHandle.grabArea )
1167 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1169 else if( actor == secondarySelectionHandle.grabArea )
1171 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1175 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1177 // Switch between pressed/release grab-handle images
1178 if( event.GetPointCount() > 0 &&
1179 mHandle[GRAB_HANDLE].actor )
1181 const TouchPoint& point = event.GetPoint(0);
1183 if( TouchPoint::Down == point.state )
1185 mHandle[GRAB_HANDLE].pressed = true;
1187 else if( ( TouchPoint::Up == point.state ) ||
1188 ( TouchPoint::Interrupted == point.state ) )
1190 mHandle[GRAB_HANDLE].pressed = false;
1193 SetHandleImage( GRAB_HANDLE );
1196 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1200 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1202 // Switch between pressed/release selection handle images
1203 if( event.GetPointCount() > 0 &&
1204 mHandle[LEFT_SELECTION_HANDLE].actor )
1206 const TouchPoint& point = event.GetPoint(0);
1208 if( TouchPoint::Down == point.state )
1210 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1212 else if( ( TouchPoint::Up == point.state ) ||
1213 ( TouchPoint::Interrupted == point.state ) )
1215 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1216 mHandlePreviousCrossed = mHandleCurrentCrossed;
1217 mHandlePanning = false;
1220 SetHandleImage( LEFT_SELECTION_HANDLE );
1223 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1227 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1229 // Switch between pressed/release selection handle images
1230 if( event.GetPointCount() > 0 &&
1231 mHandle[RIGHT_SELECTION_HANDLE].actor )
1233 const TouchPoint& point = event.GetPoint(0);
1235 if( TouchPoint::Down == point.state )
1237 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1239 else if( ( TouchPoint::Up == point.state ) ||
1240 ( TouchPoint::Interrupted == point.state ) )
1242 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1243 mHandlePreviousCrossed = mHandleCurrentCrossed;
1244 mHandlePanning = false;
1247 SetHandleImage( RIGHT_SELECTION_HANDLE );
1250 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1254 void HandleResetPosition( PropertyNotification& source )
1256 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1258 if( grabHandle.active )
1260 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1261 SetGrabHandlePosition();
1263 // Sets the grab handle image according if it's pressed, flipped, etc.
1264 SetHandleImage( GRAB_HANDLE );
1268 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1269 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1271 // Sets the primary handle image according if it's pressed, flipped, etc.
1272 SetHandleImage( LEFT_SELECTION_HANDLE );
1274 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1275 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1277 // Sets the secondary handle image according if it's pressed, flipped, etc.
1278 SetHandleImage( RIGHT_SELECTION_HANDLE );
1282 void SetupActiveLayerPropertyNotifications()
1289 // Vertical notifications.
1291 // Disconnect any previous connected callback.
1292 if( mVerticalLessThanNotification )
1294 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1295 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1298 if( mVerticalGreaterThanNotification )
1300 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1301 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1304 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1305 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1306 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1308 if( grabHandle.active )
1310 if( grabHandle.verticallyFlipped )
1312 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1313 mVerticalGreaterThanNotification.Reset();
1315 // The vertical distance from the center of the active layer to the top edje of the display.
1316 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1318 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1319 LessThanCondition( mBoundingBox.y + topHeight ) );
1321 // Notifies the change from false to true and from true to false.
1322 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1324 // Connects the signals with the callbacks.
1325 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1329 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1330 mVerticalLessThanNotification.Reset();
1332 // The vertical distance from the center of the active layer to the bottom edje of the display.
1333 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1335 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1336 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1338 // Notifies the change from false to true and from true to false.
1339 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1341 // Connects the signals with the callbacks.
1342 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1345 else // The selection handles are active
1347 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1349 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1350 mVerticalGreaterThanNotification.Reset();
1352 // The vertical distance from the center of the active layer to the top edje of the display.
1353 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1355 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1356 LessThanCondition( mBoundingBox.y + topHeight ) );
1358 // Notifies the change from false to true and from true to false.
1359 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1361 // Connects the signals with the callbacks.
1362 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1364 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1366 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1367 mVerticalLessThanNotification.Reset();
1369 // The vertical distance from the center of the active layer to the bottom edje of the display.
1370 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1371 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1373 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1374 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1376 // Notifies the change from false to true and from true to false.
1377 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1379 // Connects the signals with the callbacks.
1380 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1384 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1386 // The vertical distance from the center of the active layer to the top edje of the display.
1387 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1388 -primaryHandle.position.y + primaryHandle.size.height :
1389 -secondaryHandle.position.y + secondaryHandle.size.height );
1391 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1392 LessThanCondition( mBoundingBox.y + topHeight ) );
1394 // Notifies the change from false to true and from true to false.
1395 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1397 // Connects the signals with the callbacks.
1398 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1400 // The vertical distance from the center of the active layer to the bottom edje of the display.
1401 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1402 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1403 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1405 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1406 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1408 // Notifies the change from false to true and from true to false.
1409 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1411 // Connects the signals with the callbacks.
1412 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1416 // Horizontal notifications.
1418 // Disconnect any previous connected callback.
1419 if( mHorizontalLessThanNotification )
1421 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1422 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1425 if( mHorizontalGreaterThanNotification )
1427 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1428 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1431 if( primaryHandle.active || secondaryHandle.active )
1433 // The horizontal distance from the center of the active layer to the left edje of the display.
1434 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1435 -secondaryHandle.position.x + secondaryHandle.size.width );
1437 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1438 LessThanCondition( mBoundingBox.x + leftWidth ) );
1440 // Notifies the change from false to true and from true to false.
1441 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1443 // Connects the signals with the callbacks.
1444 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1446 // The horizontal distance from the center of the active layer to the right edje of the display.
1447 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1448 secondaryHandle.position.x + secondaryHandle.size.width );
1450 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1451 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1453 // Notifies the change from false to true and from true to false.
1454 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1456 // Connects the signals with the callbacks.
1457 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1463 float AlternatePopUpPositionRelativeToCursor()
1465 float alternativePosition = 0.0f;
1467 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1469 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1470 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1471 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1472 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1474 if( primaryHandle.active || secondaryHandle.active )
1476 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1477 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1481 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1484 return alternativePosition;
1487 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1489 float alternativeYPosition = 0.0f;
1490 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1491 // if can't be positioned above, then position below row.
1492 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1494 mCopyPastePopup.actor.SetY( alternativeYPosition );
1497 void SetUpPopupPositionNotifications()
1499 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1501 // Exceeding vertical boundary
1503 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1505 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1506 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1507 mBoundingBox.w - popupHeight * 0.5f ) );
1509 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1512 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1514 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1516 // Parent must already by added to Stage for these Get calls to work
1517 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1518 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1520 // Calculate distance to move popup (in local space) so fits within the boundary
1521 float xOffSetToKeepWithinBounds = 0.0f;
1522 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1524 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1526 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1528 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1531 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1532 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1534 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1537 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1539 // Prevent pixel mis-alignment by rounding down.
1540 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1541 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1544 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1546 HandleImpl& handle = mHandle[handleType];
1547 handle.size = Size( image.GetWidth(), image.GetHeight() );
1549 mHandleImages[handleType][handleImageType] = image;
1552 void SetScrollThreshold( float threshold )
1554 mScrollThreshold = threshold;
1557 float GetScrollThreshold() const
1559 return mScrollThreshold;
1562 void SetScrollSpeed( float speed )
1564 mScrollSpeed = speed;
1565 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1568 float GetScrollSpeed() const
1570 return mScrollSpeed;
1573 void NotifyEndOfScroll()
1579 mNotifyEndOfScroll = true;
1584 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1586 * It only starts the timer if it's already created.
1588 void StartScrollTimer()
1592 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1593 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1596 if( !mScrollTimer.IsRunning() )
1598 mScrollTimer.Start();
1603 * Stops the timer used to scroll the text.
1605 void StopScrollTimer()
1609 mScrollTimer.Stop();
1614 * Callback called by the timer used to scroll the text.
1616 * It calculates and sets a new scroll position.
1618 bool OnScrollTimerTick()
1620 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1622 mController.DecorationEvent( mHandleScrolling,
1624 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1631 ControllerInterface& mController;
1633 TapGestureDetector mTapDetector;
1634 PanGestureDetector mPanGestureDetector;
1635 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1636 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1638 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1639 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1640 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1641 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1642 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1643 Control mPrimaryCursor;
1644 Control mSecondaryCursor;
1646 Actor mHighlightActor; ///< Actor to display highlight
1647 Renderer mHighlightRenderer;
1648 Shader mHighlightShader; ///< Shader used for highlight
1649 Property::Map mQuadVertexFormat;
1650 PopupImpl mCopyPastePopup;
1651 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1652 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1654 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1655 Vector4 mHandleColor;
1657 CursorImpl mCursor[CURSOR_COUNT];
1658 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1660 PropertyBuffer mQuadVertices;
1661 Geometry mQuadGeometry;
1662 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1664 Vector4 mBoundingBox; ///< The bounding box in world coords.
1665 Vector4 mHighlightColor; ///< Color of the highlight
1666 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1667 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1669 unsigned int mActiveCursor;
1670 unsigned int mCursorBlinkInterval;
1671 float mCursorBlinkDuration;
1672 float mCursorWidth; ///< The width of the cursors in pixels.
1673 HandleType mHandleScrolling; ///< The handle which is scrolling.
1674 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1675 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1676 float mScrollSpeed; ///< The scroll speed in pixels per second.
1677 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1678 int mTextDepth; ///< The depth used to render the text.
1680 bool mActiveCopyPastePopup : 1;
1681 bool mPopupSetNewPosition : 1;
1682 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1683 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1684 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1685 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1686 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1687 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1688 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1689 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1690 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1691 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1692 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1695 DecoratorPtr Decorator::New( ControllerInterface& controller,
1696 TextSelectionPopupCallbackInterface& callbackInterface )
1698 return DecoratorPtr( new Decorator( controller,
1699 callbackInterface ) );
1702 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1704 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1707 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1709 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1712 void Decorator::Relayout( const Vector2& size )
1714 mImpl->Relayout( size );
1717 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1719 mImpl->UpdatePositions( scrollOffset );
1724 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1726 mImpl->mActiveCursor = activeCursor;
1729 unsigned int Decorator::GetActiveCursor() const
1731 return mImpl->mActiveCursor;
1734 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1736 mImpl->mCursor[cursor].position.x = x;
1737 mImpl->mCursor[cursor].position.y = y;
1738 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1739 mImpl->mCursor[cursor].lineHeight = lineHeight;
1742 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1744 x = mImpl->mCursor[cursor].position.x;
1745 y = mImpl->mCursor[cursor].position.y;
1746 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1747 lineHeight = mImpl->mCursor[cursor].lineHeight;
1750 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1752 return mImpl->mCursor[cursor].position;
1755 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1757 mImpl->mCursor[cursor].color = color;
1760 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1762 return mImpl->mCursor[cursor].color;
1765 void Decorator::StartCursorBlink()
1767 if ( !mImpl->mCursorBlinkTimer )
1769 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1770 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1773 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1775 mImpl->mCursorBlinkTimer.Start();
1779 void Decorator::StopCursorBlink()
1781 if ( mImpl->mCursorBlinkTimer )
1783 mImpl->mCursorBlinkTimer.Stop();
1786 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1789 void Decorator::DelayCursorBlink()
1791 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1792 mImpl->mDelayCursorBlink = true;
1795 void Decorator::SetCursorBlinkInterval( float seconds )
1797 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1800 float Decorator::GetCursorBlinkInterval() const
1802 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1805 void Decorator::SetCursorBlinkDuration( float seconds )
1807 mImpl->mCursorBlinkDuration = seconds;
1810 float Decorator::GetCursorBlinkDuration() const
1812 return mImpl->mCursorBlinkDuration;
1815 void Decorator::SetCursorWidth( int width )
1817 mImpl->mCursorWidth = static_cast<float>( width );
1820 int Decorator::GetCursorWidth() const
1822 return static_cast<int>( mImpl->mCursorWidth );
1827 void Decorator::SetHandleActive( HandleType handleType, bool active )
1829 mImpl->mHandle[handleType].active = active;
1833 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1835 mImpl->mHandlePreviousCrossed = false;
1838 // TODO: this is a work-around.
1839 // The problem is the handle actor does not receive the touch event with the Interrupt
1840 // state when the power button is pressed and the application goes to background.
1841 mImpl->mHandle[handleType].pressed = false;
1842 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1843 ImageView imageView = mImpl->mHandle[handleType].actor;
1844 if( imageReleased && imageView )
1846 imageView.SetImage( imageReleased );
1852 bool Decorator::IsHandleActive( HandleType handleType ) const
1854 return mImpl->mHandle[handleType].active ;
1857 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1859 mImpl->SetHandleImage( handleType, handleImageType, image );
1862 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1864 return mImpl->mHandleImages[handleType][handleImageType];
1867 void Decorator::SetHandleColor( const Vector4& color )
1869 mImpl->mHandleColor = color;
1872 const Vector4& Decorator::GetHandleColor() const
1874 return mImpl->mHandleColor;
1877 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1879 // Adjust handle's displacement
1880 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1882 handle.grabDisplacementX -= x - handle.position.x;
1883 handle.grabDisplacementY -= y - handle.position.y;
1885 handle.position.x = x;
1886 handle.position.y = y;
1887 handle.lineHeight = height;
1890 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1892 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1894 x = handle.position.x;
1895 y = handle.position.y;
1896 height = handle.lineHeight;
1899 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1901 return mImpl->mHandle[handleType].position;
1904 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1906 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1909 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1911 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1914 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1916 mImpl->mFlipSelectionHandlesOnCross = enable;
1919 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1921 mImpl->mHandleCurrentCrossed = indicesSwapped;
1922 mImpl->mFlipLeftSelectionHandleDirection = left;
1923 mImpl->mFlipRightSelectionHandleDirection = right;
1926 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1928 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1931 void Decorator::ClearHighlights()
1933 mImpl->mHighlightQuadList.clear();
1934 mImpl->mHighlightPosition = Vector2::ZERO;
1937 void Decorator::SetHighlightColor( const Vector4& color )
1939 mImpl->mHighlightColor = color;
1942 const Vector4& Decorator::GetHighlightColor() const
1944 return mImpl->mHighlightColor;
1947 void Decorator::SetTextDepth( int textDepth )
1949 mImpl->mTextDepth = textDepth;
1952 void Decorator::SetPopupActive( bool active )
1954 mImpl->mActiveCopyPastePopup = active;
1957 bool Decorator::IsPopupActive() const
1959 return mImpl->mActiveCopyPastePopup ;
1962 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1964 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1966 if ( !mImpl->mCopyPastePopup.actor )
1968 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1969 #ifdef DECORATOR_DEBUG
1970 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1972 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1973 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1976 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1979 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1981 return mImpl->mEnabledPopupButtons;
1986 void Decorator::SetScrollThreshold( float threshold )
1988 mImpl->SetScrollThreshold( threshold );
1991 float Decorator::GetScrollThreshold() const
1993 return mImpl->GetScrollThreshold();
1996 void Decorator::SetScrollSpeed( float speed )
1998 mImpl->SetScrollSpeed( speed );
2001 float Decorator::GetScrollSpeed() const
2003 return mImpl->GetScrollSpeed();
2006 void Decorator::NotifyEndOfScroll()
2008 mImpl->NotifyEndOfScroll();
2011 Decorator::~Decorator()
2016 Decorator::Decorator( ControllerInterface& controller,
2017 TextSelectionPopupCallbackInterface& callbackInterface )
2020 mImpl = new Decorator::Impl( controller, callbackInterface );
2025 } // namespace Toolkit