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 mQuadIndexFormat[ "indices" ] = Property::INTEGER;
287 mHighlightShader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER );
292 * Relayout of the decorations owned by the decorator.
293 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
295 void Relayout( const Vector2& size )
299 // TODO - Remove this if nothing is active
302 // Show or hide the cursors
307 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
308 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
309 if( mPrimaryCursorVisible )
311 mPrimaryCursor.SetPosition( cursor.position.x,
313 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
315 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
317 if( mSecondaryCursor )
319 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
320 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
321 if( mSecondaryCursorVisible )
323 mSecondaryCursor.SetPosition( cursor.position.x,
325 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
327 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
330 // Show or hide the grab handle
331 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
332 bool newGrabHandlePosition = false;
333 if( grabHandle.active )
335 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
341 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
342 SetGrabHandlePosition();
344 // Sets the grab handle image according if it's pressed, flipped, etc.
345 SetHandleImage( GRAB_HANDLE );
347 newGrabHandlePosition = true;
350 if( grabHandle.actor )
352 grabHandle.actor.SetVisible( isVisible );
355 else if( grabHandle.actor )
357 grabHandle.actor.Unparent();
360 // Show or hide the selection handles/highlight
361 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
362 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
363 bool newPrimaryHandlePosition = false;
364 bool newSecondaryHandlePosition = false;
365 if( primary.active || secondary.active )
367 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
368 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
370 if( isPrimaryVisible || isSecondaryVisible )
372 CreateSelectionHandles();
374 if( isPrimaryVisible )
376 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
378 // Sets the primary handle image according if it's pressed, flipped, etc.
379 SetHandleImage( LEFT_SELECTION_HANDLE );
381 SetSelectionHandleMarkerSize( primary );
383 newPrimaryHandlePosition = true;
386 if( isSecondaryVisible )
388 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
390 // Sets the secondary handle image according if it's pressed, flipped, etc.
391 SetHandleImage( RIGHT_SELECTION_HANDLE );
393 SetSelectionHandleMarkerSize( secondary );
395 newSecondaryHandlePosition = true;
401 primary.actor.SetVisible( isPrimaryVisible );
403 if( secondary.actor )
405 secondary.actor.SetVisible( isSecondaryVisible );
415 primary.actor.Unparent();
417 if( secondary.actor )
419 secondary.actor.Unparent();
421 if( mHighlightActor )
423 mHighlightActor.Unparent();
427 if( newGrabHandlePosition ||
428 newPrimaryHandlePosition ||
429 newSecondaryHandlePosition )
431 // Setup property notifications to find whether the handles leave the boundaries of the current display.
432 SetupActiveLayerPropertyNotifications();
435 if( mActiveCopyPastePopup )
438 mPopupSetNewPosition = true;
442 if( mCopyPastePopup.actor )
444 mCopyPastePopup.actor.HidePopup();
445 mPopupSetNewPosition = true;
450 void UpdatePositions( const Vector2& scrollOffset )
452 mCursor[PRIMARY_CURSOR].position += scrollOffset;
453 mCursor[SECONDARY_CURSOR].position += scrollOffset;
454 mHandle[ GRAB_HANDLE ].position += scrollOffset;
455 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
456 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
457 mHighlightPosition += scrollOffset;
462 if ( !mCopyPastePopup.actor )
467 if( !mCopyPastePopup.actor.GetParent() )
469 mActiveLayer.Add( mCopyPastePopup.actor );
472 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
473 mCopyPastePopup.actor.ShowPopup();
476 void DeterminePositionPopup()
478 if( !mActiveCopyPastePopup )
483 // Retrieves the popup's size after relayout.
484 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
486 if( mPopupSetNewPosition )
488 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
489 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
490 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
491 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
493 if( primaryHandle.active || secondaryHandle.active )
495 // Calculates the popup's position if selection handles are active.
496 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
497 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
498 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
500 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
501 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
505 // Calculates the popup's position if the grab handle is active.
506 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
510 // Checks if there is enough space above the text control. If not it places the popup under it.
511 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
513 SetUpPopupPositionNotifications();
515 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
516 mPopupSetNewPosition = false;
519 void PopupRelayoutComplete( Actor actor )
521 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
523 DeterminePositionPopup();
526 void CreateCursor( Control& cursor, const Vector4& color )
528 cursor = Control::New();
529 cursor.SetBackgroundColor( color );
530 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT );
531 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
534 // Add or Remove cursor(s) from parent
537 if( mActiveCursor == ACTIVE_CURSOR_NONE )
541 mPrimaryCursor.Unparent();
543 if( mSecondaryCursor )
545 mSecondaryCursor.Unparent();
550 // Create Primary and or Secondary Cursor(s) if active and add to parent
551 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
552 mActiveCursor == ACTIVE_CURSOR_BOTH )
554 if ( !mPrimaryCursor )
556 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
557 #ifdef DECORATOR_DEBUG
558 mPrimaryCursor.SetName( "PrimaryCursorActor" );
562 if( !mPrimaryCursor.GetParent() )
564 mActiveLayer.Add( mPrimaryCursor );
568 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
570 if ( !mSecondaryCursor )
572 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
573 #ifdef DECORATOR_DEBUG
574 mSecondaryCursor.SetName( "SecondaryCursorActor" );
578 if( !mSecondaryCursor.GetParent() )
580 mActiveLayer.Add( mSecondaryCursor );
585 if( mSecondaryCursor )
587 mSecondaryCursor.Unparent();
593 bool OnCursorBlinkTimerTick()
595 if( !mDelayCursorBlink )
598 if ( mPrimaryCursor )
600 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
602 if ( mSecondaryCursor )
604 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
607 mCursorBlinkStatus = !mCursorBlinkStatus;
612 mDelayCursorBlink = false;
618 void SetupTouchEvents()
620 mTapDetector = TapGestureDetector::New();
621 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
623 mPanGestureDetector = PanGestureDetector::New();
624 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
627 void CreateActiveLayer()
631 mActiveLayer = Layer::New();
632 #ifdef DECORATOR_DEBUG
633 mActiveLayer.SetName ( "ActiveLayerActor" );
636 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
637 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
639 // Add the active layer telling the controller it doesn't need clipping.
640 mController.AddDecoration( mActiveLayer, false );
643 mActiveLayer.RaiseToTop();
646 void SetSelectionHandleMarkerSize( HandleImpl& handle )
648 if( handle.markerActor )
650 handle.markerActor.SetSize( 0, handle.lineHeight );
654 void CreateGrabHandle()
656 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
657 if( !grabHandle.actor )
659 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
661 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
662 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
663 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
665 // Area that Grab handle responds to, larger than actual handle so easier to move
666 #ifdef DECORATOR_DEBUG
667 grabHandle.actor.SetName( "GrabHandleActor" );
668 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
670 grabHandle.grabArea = Control::New();
671 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
672 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
673 grabHandle.grabArea.SetName( "GrabArea" );
677 grabHandle.grabArea = Actor::New();
678 grabHandle.grabArea.SetName( "GrabArea" );
681 grabHandle.grabArea = Actor::New();
684 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
685 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
686 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
687 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
688 grabHandle.actor.Add( grabHandle.grabArea );
689 grabHandle.actor.SetColor( mHandleColor );
691 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
692 mTapDetector.Attach( grabHandle.grabArea );
693 mPanGestureDetector.Attach( grabHandle.grabArea );
695 mActiveLayer.Add( grabHandle.actor );
699 if( grabHandle.actor && !grabHandle.actor.GetParent() )
701 mActiveLayer.Add( grabHandle.actor );
705 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
709 handle.markerActor = ImageView::New( image );
710 handle.markerActor.SetColor( mHandleColor );
711 handle.actor.Add( handle.markerActor );
713 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
715 if( LEFT_SELECTION_HANDLE == handleType )
717 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
718 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
720 else if( RIGHT_SELECTION_HANDLE == handleType )
722 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
723 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
728 void CreateSelectionHandles()
730 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
733 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
735 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
736 #ifdef DECORATOR_DEBUG
737 primary.actor.SetName("SelectionHandleOne");
739 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
740 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
741 primary.actor.SetColor( mHandleColor );
743 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
744 #ifdef DECORATOR_DEBUG
745 primary.grabArea.SetName("SelectionHandleOneGrabArea");
747 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
748 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
749 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
750 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
752 mTapDetector.Attach( primary.grabArea );
753 mPanGestureDetector.Attach( primary.grabArea );
754 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
756 primary.actor.Add( primary.grabArea );
758 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
762 if( primary.actor && !primary.actor.GetParent() )
764 mActiveLayer.Add( primary.actor );
767 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
768 if( !secondary.actor )
770 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
772 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
773 #ifdef DECORATOR_DEBUG
774 secondary.actor.SetName("SelectionHandleTwo");
776 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
777 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
778 secondary.actor.SetColor( mHandleColor );
780 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
781 #ifdef DECORATOR_DEBUG
782 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
784 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
785 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
786 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
787 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
789 mTapDetector.Attach( secondary.grabArea );
790 mPanGestureDetector.Attach( secondary.grabArea );
791 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
793 secondary.actor.Add( secondary.grabArea );
795 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
799 if( secondary.actor && !secondary.actor.GetParent() )
801 mActiveLayer.Add( secondary.actor );
805 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
807 // Gets the world position of the active layer. The active layer is where the handles are added.
808 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
810 // The grab handle position in world coords.
811 // The active layer's world position is the center of the active layer. The origin of the
812 // coord system of the handles is the top left of the active layer.
813 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
814 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
817 void SetGrabHandlePosition()
819 // Reference to the grab handle.
820 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
822 // Transforms the handle position into world coordinates.
823 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
824 // as it's transforming the handle's position set by the text-controller and not
825 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
826 // retrieves the position of the center of the actor but the handle's position set
827 // by the text controller is not the center of the actor.
828 Vector2 grabHandleWorldPosition;
829 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
831 // Check if the grab handle exceeds the boundaries of the decoration box.
832 // At the moment only the height is checked for the grab handle.
834 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
835 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
836 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
838 // The grab handle 'y' position in local coords.
839 // If the grab handle exceeds the bottom of the decoration box,
840 // set the 'y' position to the top of the line.
841 // The SetGrabHandleImage() method will change the orientation.
842 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
844 if( grabHandle.actor )
846 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
847 yLocalPosition ); // TODO : Fix for multiline.
851 void SetSelectionHandlePosition( HandleType type )
853 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
855 // Reference to the selection handle.
856 HandleImpl& handle = mHandle[type];
858 // Transforms the handle position into world coordinates.
859 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
860 // as it's transforming the handle's position set by the text-controller and not
861 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
862 // retrieves the position of the center of the actor but the handle's position set
863 // by the text controller is not the center of the actor.
864 Vector2 handleWorldPosition;
865 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
867 // Whether to flip the handle (horizontally).
868 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
870 // Whether to flip the handles if they are crossed.
871 bool crossFlip = false;
872 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
874 crossFlip = mHandleCurrentCrossed;
877 // Does not flip if both conditions are true (double flip)
878 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
880 // Will flip the handles vertically if the user prefers it.
881 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
883 if( crossFlip || mHandlePreviousCrossed )
885 if( isPrimaryHandle )
887 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
891 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
895 // Check if the selection handle exceeds the boundaries of the decoration box.
896 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
897 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
899 // Does not flip if both conditions are true (double flip)
900 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
904 if( handle.actor && !handle.horizontallyFlipped )
906 // Change the anchor point to flip the image.
907 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
909 handle.horizontallyFlipped = true;
914 if( handle.actor && handle.horizontallyFlipped )
916 // Reset the anchor point.
917 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
919 handle.horizontallyFlipped = false;
923 // Whether to flip the handle vertically.
924 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
925 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
926 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
928 // The primary selection handle 'y' position in local coords.
929 // If the handle exceeds the bottom of the decoration box,
930 // set the 'y' position to the top of the line.
931 // The SetHandleImage() method will change the orientation.
932 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
936 handle.actor.SetPosition( handle.position.x,
937 yLocalPosition ); // TODO : Fix for multiline.
941 void SetHandleImage( HandleType type )
943 HandleImpl& handle = mHandle[type];
945 HandleType markerType = HANDLE_TYPE_COUNT;
946 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
947 if( LEFT_SELECTION_HANDLE == type )
949 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
950 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
952 else if( RIGHT_SELECTION_HANDLE == type )
954 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
955 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
958 // Chooses between the released or pressed image. It checks whether the pressed image exists.
961 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
963 handle.actor.SetImage( mHandleImages[type][imageType] );
966 if( HANDLE_TYPE_COUNT != markerType )
968 if( handle.markerActor )
970 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
971 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
975 // Whether to flip the handle vertically.
978 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
982 void CreateHighlight()
984 if( !mHighlightActor )
986 mHighlightActor = Actor::New();
988 #ifdef DECORATOR_DEBUG
989 mHighlightActor.SetName( "HighlightActor" );
991 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
992 mHighlightActor.SetSize( 1.0f, 1.0f );
993 mHighlightActor.SetColor( mHighlightColor );
994 mHighlightActor.SetColorMode( USE_OWN_COLOR );
997 // Add the highlight box telling the controller it needs clipping.
998 mController.AddDecoration( mHighlightActor, true );
1001 void UpdateHighlight()
1003 if ( mHighlightActor )
1005 if( !mHighlightQuadList.empty() )
1007 Vector< Vector2 > vertices;
1008 Vector< unsigned int> indices;
1011 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1012 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1014 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1016 QuadCoordinates& quad = *iter;
1019 vertex.x = quad.min.x;
1020 vertex.y = quad.min.y;
1021 vertices.PushBack( vertex );
1024 vertex.x = quad.max.x;
1025 vertex.y = quad.min.y;
1026 vertices.PushBack( vertex );
1028 // bottom-left (v+2)
1029 vertex.x = quad.min.x;
1030 vertex.y = quad.max.y;
1031 vertices.PushBack( vertex );
1033 // bottom-right (v+3)
1034 vertex.x = quad.max.x;
1035 vertex.y = quad.max.y;
1036 vertices.PushBack( vertex );
1038 // triangle A (3, 1, 0)
1039 indices.PushBack( v + 3 );
1040 indices.PushBack( v + 1 );
1041 indices.PushBack( v );
1043 // triangle B (0, 2, 3)
1044 indices.PushBack( v );
1045 indices.PushBack( v + 2 );
1046 indices.PushBack( v + 3 );
1049 if( ! mQuadVertices )
1051 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1054 if( ! mQuadIndices )
1056 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat );
1059 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1060 mQuadIndices.SetData( &indices[ 0 ], indices.Size() );
1062 if( !mQuadGeometry )
1064 mQuadGeometry = Geometry::New();
1065 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1067 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1069 if( !mHighlightRenderer )
1071 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightShader );
1072 mHighlightActor.AddRenderer( mHighlightRenderer );
1076 mHighlightActor.SetPosition( mHighlightPosition.x,
1077 mHighlightPosition.y );
1079 mHighlightQuadList.clear();
1081 if( mHighlightRenderer )
1083 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1088 void OnTap( Actor actor, const TapGesture& tap )
1090 if( actor == mHandle[GRAB_HANDLE].actor )
1096 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1098 if( Gesture::Started == gesture.state )
1100 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1103 handle.grabDisplacementX += gesture.displacement.x;
1104 handle.grabDisplacementY += gesture.displacement.y;
1106 const float x = handle.position.x + handle.grabDisplacementX;
1107 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1109 if( Gesture::Started == gesture.state ||
1110 Gesture::Continuing == gesture.state )
1113 mController.GetTargetSize( targetSize );
1115 if( x < mScrollThreshold )
1117 mScrollDirection = SCROLL_RIGHT;
1118 mHandleScrolling = type;
1121 else if( x > targetSize.width - mScrollThreshold )
1123 mScrollDirection = SCROLL_LEFT;
1124 mHandleScrolling = type;
1129 mHandleScrolling = HANDLE_TYPE_COUNT;
1131 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1134 mHandlePanning = true;
1136 else if( Gesture::Finished == gesture.state ||
1137 Gesture::Cancelled == gesture.state )
1140 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1142 mNotifyEndOfScroll = false;
1143 mHandleScrolling = HANDLE_TYPE_COUNT;
1145 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1149 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1154 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1156 handle.pressed = false;
1158 mHandlePanning = false;
1162 void OnPan( Actor actor, const PanGesture& gesture )
1164 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1165 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1166 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1168 if( actor == grabHandle.grabArea )
1170 DoPan( grabHandle, GRAB_HANDLE, gesture );
1172 else if( actor == primarySelectionHandle.grabArea )
1174 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1176 else if( actor == secondarySelectionHandle.grabArea )
1178 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1182 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1184 // Switch between pressed/release grab-handle images
1185 if( event.GetPointCount() > 0 &&
1186 mHandle[GRAB_HANDLE].actor )
1188 const TouchPoint& point = event.GetPoint(0);
1190 if( TouchPoint::Down == point.state )
1192 mHandle[GRAB_HANDLE].pressed = true;
1194 else if( ( TouchPoint::Up == point.state ) ||
1195 ( TouchPoint::Interrupted == point.state ) )
1197 mHandle[GRAB_HANDLE].pressed = false;
1200 SetHandleImage( GRAB_HANDLE );
1203 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1207 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1209 // Switch between pressed/release selection handle images
1210 if( event.GetPointCount() > 0 &&
1211 mHandle[LEFT_SELECTION_HANDLE].actor )
1213 const TouchPoint& point = event.GetPoint(0);
1215 if( TouchPoint::Down == point.state )
1217 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1219 else if( ( TouchPoint::Up == point.state ) ||
1220 ( TouchPoint::Interrupted == point.state ) )
1222 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1223 mHandlePreviousCrossed = mHandleCurrentCrossed;
1224 mHandlePanning = false;
1227 SetHandleImage( LEFT_SELECTION_HANDLE );
1230 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1234 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1236 // Switch between pressed/release selection handle images
1237 if( event.GetPointCount() > 0 &&
1238 mHandle[RIGHT_SELECTION_HANDLE].actor )
1240 const TouchPoint& point = event.GetPoint(0);
1242 if( TouchPoint::Down == point.state )
1244 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1246 else if( ( TouchPoint::Up == point.state ) ||
1247 ( TouchPoint::Interrupted == point.state ) )
1249 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1250 mHandlePreviousCrossed = mHandleCurrentCrossed;
1251 mHandlePanning = false;
1254 SetHandleImage( RIGHT_SELECTION_HANDLE );
1257 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1261 void HandleResetPosition( PropertyNotification& source )
1263 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1265 if( grabHandle.active )
1267 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1268 SetGrabHandlePosition();
1270 // Sets the grab handle image according if it's pressed, flipped, etc.
1271 SetHandleImage( GRAB_HANDLE );
1275 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1276 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1278 // Sets the primary handle image according if it's pressed, flipped, etc.
1279 SetHandleImage( LEFT_SELECTION_HANDLE );
1281 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1282 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1284 // Sets the secondary handle image according if it's pressed, flipped, etc.
1285 SetHandleImage( RIGHT_SELECTION_HANDLE );
1289 void SetupActiveLayerPropertyNotifications()
1296 // Vertical notifications.
1298 // Disconnect any previous connected callback.
1299 if( mVerticalLessThanNotification )
1301 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1302 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1305 if( mVerticalGreaterThanNotification )
1307 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1308 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1311 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1312 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1313 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1315 if( grabHandle.active )
1317 if( grabHandle.verticallyFlipped )
1319 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1320 mVerticalGreaterThanNotification.Reset();
1322 // The vertical distance from the center of the active layer to the top edje of the display.
1323 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1325 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1326 LessThanCondition( mBoundingBox.y + topHeight ) );
1328 // Notifies the change from false to true and from true to false.
1329 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1331 // Connects the signals with the callbacks.
1332 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1336 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1337 mVerticalLessThanNotification.Reset();
1339 // The vertical distance from the center of the active layer to the bottom edje of the display.
1340 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1342 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1343 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1345 // Notifies the change from false to true and from true to false.
1346 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1348 // Connects the signals with the callbacks.
1349 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1352 else // The selection handles are active
1354 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1356 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1357 mVerticalGreaterThanNotification.Reset();
1359 // The vertical distance from the center of the active layer to the top edje of the display.
1360 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1362 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1363 LessThanCondition( mBoundingBox.y + topHeight ) );
1365 // Notifies the change from false to true and from true to false.
1366 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1368 // Connects the signals with the callbacks.
1369 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1371 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1373 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1374 mVerticalLessThanNotification.Reset();
1376 // The vertical distance from the center of the active layer to the bottom edje of the display.
1377 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1378 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1380 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1381 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1383 // Notifies the change from false to true and from true to false.
1384 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1386 // Connects the signals with the callbacks.
1387 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1391 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1393 // The vertical distance from the center of the active layer to the top edje of the display.
1394 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1395 -primaryHandle.position.y + primaryHandle.size.height :
1396 -secondaryHandle.position.y + secondaryHandle.size.height );
1398 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1399 LessThanCondition( mBoundingBox.y + topHeight ) );
1401 // Notifies the change from false to true and from true to false.
1402 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1404 // Connects the signals with the callbacks.
1405 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1407 // The vertical distance from the center of the active layer to the bottom edje of the display.
1408 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1409 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1410 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1412 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1413 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1415 // Notifies the change from false to true and from true to false.
1416 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1418 // Connects the signals with the callbacks.
1419 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1423 // Horizontal notifications.
1425 // Disconnect any previous connected callback.
1426 if( mHorizontalLessThanNotification )
1428 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1429 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1432 if( mHorizontalGreaterThanNotification )
1434 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1435 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1438 if( primaryHandle.active || secondaryHandle.active )
1440 // The horizontal distance from the center of the active layer to the left edje of the display.
1441 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1442 -secondaryHandle.position.x + secondaryHandle.size.width );
1444 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1445 LessThanCondition( mBoundingBox.x + leftWidth ) );
1447 // Notifies the change from false to true and from true to false.
1448 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1450 // Connects the signals with the callbacks.
1451 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1453 // The horizontal distance from the center of the active layer to the right edje of the display.
1454 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1455 secondaryHandle.position.x + secondaryHandle.size.width );
1457 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1458 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1460 // Notifies the change from false to true and from true to false.
1461 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1463 // Connects the signals with the callbacks.
1464 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1470 float AlternatePopUpPositionRelativeToCursor()
1472 float alternativePosition = 0.0f;
1474 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1476 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1477 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1478 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1479 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1481 if( primaryHandle.active || secondaryHandle.active )
1483 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1484 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1488 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1491 return alternativePosition;
1494 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1496 float alternativeYPosition = 0.0f;
1497 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1498 // if can't be positioned above, then position below row.
1499 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1501 mCopyPastePopup.actor.SetY( alternativeYPosition );
1504 void SetUpPopupPositionNotifications()
1506 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1508 // Exceeding vertical boundary
1510 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1512 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1513 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1514 mBoundingBox.w - popupHeight * 0.5f ) );
1516 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1519 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1521 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1523 // Parent must already by added to Stage for these Get calls to work
1524 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1525 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1527 // Calculate distance to move popup (in local space) so fits within the boundary
1528 float xOffSetToKeepWithinBounds = 0.0f;
1529 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1531 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1533 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1535 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1538 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1539 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1541 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1544 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1546 // Prevent pixel mis-alignment by rounding down.
1547 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1548 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1551 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1553 HandleImpl& handle = mHandle[handleType];
1554 handle.size = Size( image.GetWidth(), image.GetHeight() );
1556 mHandleImages[handleType][handleImageType] = image;
1559 void SetScrollThreshold( float threshold )
1561 mScrollThreshold = threshold;
1564 float GetScrollThreshold() const
1566 return mScrollThreshold;
1569 void SetScrollSpeed( float speed )
1571 mScrollSpeed = speed;
1572 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1575 float GetScrollSpeed() const
1577 return mScrollSpeed;
1580 void NotifyEndOfScroll()
1586 mNotifyEndOfScroll = true;
1591 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1593 * It only starts the timer if it's already created.
1595 void StartScrollTimer()
1599 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1600 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1603 if( !mScrollTimer.IsRunning() )
1605 mScrollTimer.Start();
1610 * Stops the timer used to scroll the text.
1612 void StopScrollTimer()
1616 mScrollTimer.Stop();
1621 * Callback called by the timer used to scroll the text.
1623 * It calculates and sets a new scroll position.
1625 bool OnScrollTimerTick()
1627 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1629 mController.DecorationEvent( mHandleScrolling,
1631 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1638 ControllerInterface& mController;
1640 TapGestureDetector mTapDetector;
1641 PanGestureDetector mPanGestureDetector;
1642 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1643 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1645 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1646 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1647 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1648 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1649 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1650 Control mPrimaryCursor;
1651 Control mSecondaryCursor;
1653 Actor mHighlightActor; ///< Actor to display highlight
1654 Renderer mHighlightRenderer;
1655 Shader mHighlightShader; ///< Shader used for highlight
1656 Property::Map mQuadVertexFormat;
1657 Property::Map mQuadIndexFormat;
1658 PopupImpl mCopyPastePopup;
1659 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1660 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1662 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1663 Vector4 mHandleColor;
1665 CursorImpl mCursor[CURSOR_COUNT];
1666 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1668 PropertyBuffer mQuadVertices;
1669 PropertyBuffer mQuadIndices;
1670 Geometry mQuadGeometry;
1671 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1673 Vector4 mBoundingBox; ///< The bounding box in world coords.
1674 Vector4 mHighlightColor; ///< Color of the highlight
1675 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1676 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1678 unsigned int mActiveCursor;
1679 unsigned int mCursorBlinkInterval;
1680 float mCursorBlinkDuration;
1681 float mCursorWidth; ///< The width of the cursors in pixels.
1682 HandleType mHandleScrolling; ///< The handle which is scrolling.
1683 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1684 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1685 float mScrollSpeed; ///< The scroll speed in pixels per second.
1686 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1687 int mTextDepth; ///< The depth used to render the text.
1689 bool mActiveCopyPastePopup : 1;
1690 bool mPopupSetNewPosition : 1;
1691 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1692 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1693 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1694 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1695 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1696 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1697 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1698 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1699 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1700 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1701 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1704 DecoratorPtr Decorator::New( ControllerInterface& controller,
1705 TextSelectionPopupCallbackInterface& callbackInterface )
1707 return DecoratorPtr( new Decorator( controller,
1708 callbackInterface ) );
1711 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1713 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1716 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1718 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1721 void Decorator::Relayout( const Vector2& size )
1723 mImpl->Relayout( size );
1726 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1728 mImpl->UpdatePositions( scrollOffset );
1733 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1735 mImpl->mActiveCursor = activeCursor;
1738 unsigned int Decorator::GetActiveCursor() const
1740 return mImpl->mActiveCursor;
1743 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1745 mImpl->mCursor[cursor].position.x = x;
1746 mImpl->mCursor[cursor].position.y = y;
1747 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1748 mImpl->mCursor[cursor].lineHeight = lineHeight;
1751 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1753 x = mImpl->mCursor[cursor].position.x;
1754 y = mImpl->mCursor[cursor].position.y;
1755 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1756 lineHeight = mImpl->mCursor[cursor].lineHeight;
1759 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1761 return mImpl->mCursor[cursor].position;
1764 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1766 mImpl->mCursor[cursor].color = color;
1769 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1771 return mImpl->mCursor[cursor].color;
1774 void Decorator::StartCursorBlink()
1776 if ( !mImpl->mCursorBlinkTimer )
1778 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1779 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1782 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1784 mImpl->mCursorBlinkTimer.Start();
1788 void Decorator::StopCursorBlink()
1790 if ( mImpl->mCursorBlinkTimer )
1792 mImpl->mCursorBlinkTimer.Stop();
1795 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1798 void Decorator::DelayCursorBlink()
1800 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1801 mImpl->mDelayCursorBlink = true;
1804 void Decorator::SetCursorBlinkInterval( float seconds )
1806 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1809 float Decorator::GetCursorBlinkInterval() const
1811 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1814 void Decorator::SetCursorBlinkDuration( float seconds )
1816 mImpl->mCursorBlinkDuration = seconds;
1819 float Decorator::GetCursorBlinkDuration() const
1821 return mImpl->mCursorBlinkDuration;
1824 void Decorator::SetCursorWidth( int width )
1826 mImpl->mCursorWidth = static_cast<float>( width );
1829 int Decorator::GetCursorWidth() const
1831 return static_cast<int>( mImpl->mCursorWidth );
1836 void Decorator::SetHandleActive( HandleType handleType, bool active )
1838 mImpl->mHandle[handleType].active = active;
1842 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1844 mImpl->mHandlePreviousCrossed = false;
1847 // TODO: this is a work-around.
1848 // The problem is the handle actor does not receive the touch event with the Interrupt
1849 // state when the power button is pressed and the application goes to background.
1850 mImpl->mHandle[handleType].pressed = false;
1851 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1852 ImageView imageView = mImpl->mHandle[handleType].actor;
1853 if( imageReleased && imageView )
1855 imageView.SetImage( imageReleased );
1861 bool Decorator::IsHandleActive( HandleType handleType ) const
1863 return mImpl->mHandle[handleType].active ;
1866 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1868 mImpl->SetHandleImage( handleType, handleImageType, image );
1871 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1873 return mImpl->mHandleImages[handleType][handleImageType];
1876 void Decorator::SetHandleColor( const Vector4& color )
1878 mImpl->mHandleColor = color;
1881 const Vector4& Decorator::GetHandleColor() const
1883 return mImpl->mHandleColor;
1886 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1888 // Adjust handle's displacement
1889 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1891 handle.grabDisplacementX -= x - handle.position.x;
1892 handle.grabDisplacementY -= y - handle.position.y;
1894 handle.position.x = x;
1895 handle.position.y = y;
1896 handle.lineHeight = height;
1899 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1901 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1903 x = handle.position.x;
1904 y = handle.position.y;
1905 height = handle.lineHeight;
1908 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1910 return mImpl->mHandle[handleType].position;
1913 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1915 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1918 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1920 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1923 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1925 mImpl->mFlipSelectionHandlesOnCross = enable;
1928 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1930 mImpl->mHandleCurrentCrossed = indicesSwapped;
1931 mImpl->mFlipLeftSelectionHandleDirection = left;
1932 mImpl->mFlipRightSelectionHandleDirection = right;
1935 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1937 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1940 void Decorator::ClearHighlights()
1942 mImpl->mHighlightQuadList.clear();
1943 mImpl->mHighlightPosition = Vector2::ZERO;
1946 void Decorator::SetHighlightColor( const Vector4& color )
1948 mImpl->mHighlightColor = color;
1951 const Vector4& Decorator::GetHighlightColor() const
1953 return mImpl->mHighlightColor;
1956 void Decorator::SetTextDepth( int textDepth )
1958 mImpl->mTextDepth = textDepth;
1961 void Decorator::SetPopupActive( bool active )
1963 mImpl->mActiveCopyPastePopup = active;
1966 bool Decorator::IsPopupActive() const
1968 return mImpl->mActiveCopyPastePopup ;
1971 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1973 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1975 if ( !mImpl->mCopyPastePopup.actor )
1977 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1978 #ifdef DECORATOR_DEBUG
1979 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1981 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1982 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1985 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1988 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1990 return mImpl->mEnabledPopupButtons;
1995 void Decorator::SetScrollThreshold( float threshold )
1997 mImpl->SetScrollThreshold( threshold );
2000 float Decorator::GetScrollThreshold() const
2002 return mImpl->GetScrollThreshold();
2005 void Decorator::SetScrollSpeed( float speed )
2007 mImpl->SetScrollSpeed( speed );
2010 float Decorator::GetScrollSpeed() const
2012 return mImpl->GetScrollSpeed();
2015 void Decorator::NotifyEndOfScroll()
2017 mImpl->NotifyEndOfScroll();
2020 Decorator::~Decorator()
2025 Decorator::Decorator( ControllerInterface& controller,
2026 TextSelectionPopupCallbackInterface& callbackInterface )
2029 mImpl = new Decorator::Impl( controller, callbackInterface );
2034 } // namespace Toolkit