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 CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
491 if( primaryHandle.active || secondaryHandle.active )
493 // Calculates the popup's position if selection handles are active.
494 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
495 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
496 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
498 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
499 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
503 // Calculates the popup's position if the grab handle is active.
504 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
505 if( grabHandle.verticallyFlipped )
507 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
511 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height + cursor.position.y, 0.0f );
516 // Checks if there is enough space above the text control. If not it places the popup under it.
517 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
519 SetUpPopupPositionNotifications();
521 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
522 mPopupSetNewPosition = false;
525 void PopupRelayoutComplete( Actor actor )
527 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
529 DeterminePositionPopup();
532 void CreateCursor( Control& cursor, const Vector4& color )
534 cursor = Control::New();
535 cursor.SetBackgroundColor( color );
536 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT );
537 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
540 // Add or Remove cursor(s) from parent
543 if( mActiveCursor == ACTIVE_CURSOR_NONE )
547 mPrimaryCursor.Unparent();
549 if( mSecondaryCursor )
551 mSecondaryCursor.Unparent();
556 // Create Primary and or Secondary Cursor(s) if active and add to parent
557 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
558 mActiveCursor == ACTIVE_CURSOR_BOTH )
560 if ( !mPrimaryCursor )
562 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
563 #ifdef DECORATOR_DEBUG
564 mPrimaryCursor.SetName( "PrimaryCursorActor" );
568 if( !mPrimaryCursor.GetParent() )
570 mActiveLayer.Add( mPrimaryCursor );
574 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
576 if ( !mSecondaryCursor )
578 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
579 #ifdef DECORATOR_DEBUG
580 mSecondaryCursor.SetName( "SecondaryCursorActor" );
584 if( !mSecondaryCursor.GetParent() )
586 mActiveLayer.Add( mSecondaryCursor );
591 if( mSecondaryCursor )
593 mSecondaryCursor.Unparent();
599 bool OnCursorBlinkTimerTick()
601 if( !mDelayCursorBlink )
604 if ( mPrimaryCursor )
606 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
608 if ( mSecondaryCursor )
610 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
613 mCursorBlinkStatus = !mCursorBlinkStatus;
618 mDelayCursorBlink = false;
624 void SetupTouchEvents()
626 mTapDetector = TapGestureDetector::New();
627 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
629 mPanGestureDetector = PanGestureDetector::New();
630 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
633 void CreateActiveLayer()
637 mActiveLayer = Layer::New();
638 #ifdef DECORATOR_DEBUG
639 mActiveLayer.SetName ( "ActiveLayerActor" );
642 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
643 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
645 // Add the active layer telling the controller it doesn't need clipping.
646 mController.AddDecoration( mActiveLayer, false );
649 mActiveLayer.RaiseToTop();
652 void SetSelectionHandleMarkerSize( HandleImpl& handle )
654 if( handle.markerActor )
656 handle.markerActor.SetSize( 0, handle.lineHeight );
660 void CreateGrabHandle()
662 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
663 if( !grabHandle.actor )
665 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
667 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
668 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
669 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
671 // Area that Grab handle responds to, larger than actual handle so easier to move
672 #ifdef DECORATOR_DEBUG
673 grabHandle.actor.SetName( "GrabHandleActor" );
674 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
676 grabHandle.grabArea = Control::New();
677 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
678 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
679 grabHandle.grabArea.SetName( "GrabArea" );
683 grabHandle.grabArea = Actor::New();
684 grabHandle.grabArea.SetName( "GrabArea" );
687 grabHandle.grabArea = Actor::New();
690 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
691 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
692 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
693 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
694 grabHandle.actor.Add( grabHandle.grabArea );
695 grabHandle.actor.SetColor( mHandleColor );
697 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
698 mTapDetector.Attach( grabHandle.grabArea );
699 mPanGestureDetector.Attach( grabHandle.grabArea );
701 mActiveLayer.Add( grabHandle.actor );
705 if( grabHandle.actor && !grabHandle.actor.GetParent() )
707 mActiveLayer.Add( grabHandle.actor );
711 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
715 handle.markerActor = ImageView::New( image );
716 handle.markerActor.SetColor( mHandleColor );
717 handle.actor.Add( handle.markerActor );
719 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
721 if( LEFT_SELECTION_HANDLE == handleType )
723 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
724 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
726 else if( RIGHT_SELECTION_HANDLE == handleType )
728 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
729 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
734 void CreateSelectionHandles()
736 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
739 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
741 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
742 #ifdef DECORATOR_DEBUG
743 primary.actor.SetName("SelectionHandleOne");
745 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
746 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
747 primary.actor.SetColor( mHandleColor );
749 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
750 #ifdef DECORATOR_DEBUG
751 primary.grabArea.SetName("SelectionHandleOneGrabArea");
753 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
754 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
755 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
756 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
758 mTapDetector.Attach( primary.grabArea );
759 mPanGestureDetector.Attach( primary.grabArea );
760 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
762 primary.actor.Add( primary.grabArea );
764 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
768 if( primary.actor && !primary.actor.GetParent() )
770 mActiveLayer.Add( primary.actor );
773 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
774 if( !secondary.actor )
776 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
778 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
779 #ifdef DECORATOR_DEBUG
780 secondary.actor.SetName("SelectionHandleTwo");
782 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
783 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
784 secondary.actor.SetColor( mHandleColor );
786 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
787 #ifdef DECORATOR_DEBUG
788 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
790 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
791 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
792 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
793 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
795 mTapDetector.Attach( secondary.grabArea );
796 mPanGestureDetector.Attach( secondary.grabArea );
797 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
799 secondary.actor.Add( secondary.grabArea );
801 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
805 if( secondary.actor && !secondary.actor.GetParent() )
807 mActiveLayer.Add( secondary.actor );
811 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
813 // Gets the world position of the active layer. The active layer is where the handles are added.
814 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
816 // The grab handle position in world coords.
817 // The active layer's world position is the center of the active layer. The origin of the
818 // coord system of the handles is the top left of the active layer.
819 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
820 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
823 void SetGrabHandlePosition()
825 // Reference to the grab handle.
826 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
828 // Transforms the handle position into world coordinates.
829 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
830 // as it's transforming the handle's position set by the text-controller and not
831 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
832 // retrieves the position of the center of the actor but the handle's position set
833 // by the text controller is not the center of the actor.
834 Vector2 grabHandleWorldPosition;
835 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
837 // Check if the grab handle exceeds the boundaries of the decoration box.
838 // At the moment only the height is checked for the grab handle.
840 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
841 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
842 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
844 // The grab handle 'y' position in local coords.
845 // If the grab handle exceeds the bottom of the decoration box,
846 // set the 'y' position to the top of the line.
847 // The SetGrabHandleImage() method will change the orientation.
848 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
850 if( grabHandle.actor )
852 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
853 yLocalPosition ); // TODO : Fix for multiline.
857 void SetSelectionHandlePosition( HandleType type )
859 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
861 // Reference to the selection handle.
862 HandleImpl& handle = mHandle[type];
864 // Transforms the handle position into world coordinates.
865 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
866 // as it's transforming the handle's position set by the text-controller and not
867 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
868 // retrieves the position of the center of the actor but the handle's position set
869 // by the text controller is not the center of the actor.
870 Vector2 handleWorldPosition;
871 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
873 // Whether to flip the handle (horizontally).
874 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
876 // Whether to flip the handles if they are crossed.
877 bool crossFlip = false;
878 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
880 crossFlip = mHandleCurrentCrossed;
883 // Does not flip if both conditions are true (double flip)
884 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
886 // Will flip the handles vertically if the user prefers it.
887 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
889 if( crossFlip || mHandlePreviousCrossed )
891 if( isPrimaryHandle )
893 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
897 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
901 // Check if the selection handle exceeds the boundaries of the decoration box.
902 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
903 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
905 // Does not flip if both conditions are true (double flip)
906 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
910 if( handle.actor && !handle.horizontallyFlipped )
912 // Change the anchor point to flip the image.
913 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
915 handle.horizontallyFlipped = true;
920 if( handle.actor && handle.horizontallyFlipped )
922 // Reset the anchor point.
923 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
925 handle.horizontallyFlipped = false;
929 // Whether to flip the handle vertically.
930 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
931 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
932 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
934 // The primary selection handle 'y' position in local coords.
935 // If the handle exceeds the bottom of the decoration box,
936 // set the 'y' position to the top of the line.
937 // The SetHandleImage() method will change the orientation.
938 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
942 handle.actor.SetPosition( handle.position.x,
943 yLocalPosition ); // TODO : Fix for multiline.
947 void SetHandleImage( HandleType type )
949 HandleImpl& handle = mHandle[type];
951 HandleType markerType = HANDLE_TYPE_COUNT;
952 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
953 if( LEFT_SELECTION_HANDLE == type )
955 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
956 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
958 else if( RIGHT_SELECTION_HANDLE == type )
960 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
961 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
964 // Chooses between the released or pressed image. It checks whether the pressed image exists.
967 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
969 handle.actor.SetImage( mHandleImages[type][imageType] );
972 if( HANDLE_TYPE_COUNT != markerType )
974 if( handle.markerActor )
976 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
977 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
981 // Whether to flip the handle vertically.
984 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
988 void CreateHighlight()
990 if( !mHighlightActor )
992 mHighlightActor = Actor::New();
994 #ifdef DECORATOR_DEBUG
995 mHighlightActor.SetName( "HighlightActor" );
997 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
998 mHighlightActor.SetSize( 1.0f, 1.0f );
999 mHighlightActor.SetColor( mHighlightColor );
1000 mHighlightActor.SetColorMode( USE_OWN_COLOR );
1003 // Add the highlight box telling the controller it needs clipping.
1004 mController.AddDecoration( mHighlightActor, true );
1007 void UpdateHighlight()
1009 if ( mHighlightActor )
1011 if( !mHighlightQuadList.empty() )
1013 Vector< Vector2 > vertices;
1014 Vector< unsigned short> indices;
1017 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1018 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1020 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1022 QuadCoordinates& quad = *iter;
1025 vertex.x = quad.min.x;
1026 vertex.y = quad.min.y;
1027 vertices.PushBack( vertex );
1030 vertex.x = quad.max.x;
1031 vertex.y = quad.min.y;
1032 vertices.PushBack( vertex );
1034 // bottom-left (v+2)
1035 vertex.x = quad.min.x;
1036 vertex.y = quad.max.y;
1037 vertices.PushBack( vertex );
1039 // bottom-right (v+3)
1040 vertex.x = quad.max.x;
1041 vertex.y = quad.max.y;
1042 vertices.PushBack( vertex );
1044 // triangle A (3, 1, 0)
1045 indices.PushBack( v + 3 );
1046 indices.PushBack( v + 1 );
1047 indices.PushBack( v );
1049 // triangle B (0, 2, 3)
1050 indices.PushBack( v );
1051 indices.PushBack( v + 2 );
1052 indices.PushBack( v + 3 );
1055 if( ! mQuadVertices )
1057 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1060 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1062 if( !mQuadGeometry )
1064 mQuadGeometry = Geometry::New();
1065 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1067 mQuadGeometry.SetIndexBuffer( &indices[ 0 ], indices.Size() );
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.f;
1103 handle.grabDisplacementX += gesture.displacement.x;
1104 handle.grabDisplacementY += ( handle.verticallyFlipped ? -gesture.displacement.y : 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 PopupImpl mCopyPastePopup;
1658 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1659 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1661 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1662 Vector4 mHandleColor;
1664 CursorImpl mCursor[CURSOR_COUNT];
1665 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1667 PropertyBuffer mQuadVertices;
1668 Geometry mQuadGeometry;
1669 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1671 Vector4 mBoundingBox; ///< The bounding box in world coords.
1672 Vector4 mHighlightColor; ///< Color of the highlight
1673 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1674 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1676 unsigned int mActiveCursor;
1677 unsigned int mCursorBlinkInterval;
1678 float mCursorBlinkDuration;
1679 float mCursorWidth; ///< The width of the cursors in pixels.
1680 HandleType mHandleScrolling; ///< The handle which is scrolling.
1681 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1682 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1683 float mScrollSpeed; ///< The scroll speed in pixels per second.
1684 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1685 int mTextDepth; ///< The depth used to render the text.
1687 bool mActiveCopyPastePopup : 1;
1688 bool mPopupSetNewPosition : 1;
1689 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1690 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1691 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1692 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1693 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1694 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1695 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1696 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1697 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1698 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1699 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1702 DecoratorPtr Decorator::New( ControllerInterface& controller,
1703 TextSelectionPopupCallbackInterface& callbackInterface )
1705 return DecoratorPtr( new Decorator( controller,
1706 callbackInterface ) );
1709 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1711 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1714 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1716 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1719 void Decorator::Relayout( const Vector2& size )
1721 mImpl->Relayout( size );
1724 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1726 mImpl->UpdatePositions( scrollOffset );
1731 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1733 mImpl->mActiveCursor = activeCursor;
1736 unsigned int Decorator::GetActiveCursor() const
1738 return mImpl->mActiveCursor;
1741 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1743 mImpl->mCursor[cursor].position.x = x;
1744 mImpl->mCursor[cursor].position.y = y;
1745 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1746 mImpl->mCursor[cursor].lineHeight = lineHeight;
1749 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1751 x = mImpl->mCursor[cursor].position.x;
1752 y = mImpl->mCursor[cursor].position.y;
1753 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1754 lineHeight = mImpl->mCursor[cursor].lineHeight;
1757 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1759 return mImpl->mCursor[cursor].position;
1762 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1764 mImpl->mCursor[cursor].color = color;
1767 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1769 return mImpl->mCursor[cursor].color;
1772 void Decorator::StartCursorBlink()
1774 if ( !mImpl->mCursorBlinkTimer )
1776 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1777 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1780 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1782 mImpl->mCursorBlinkTimer.Start();
1786 void Decorator::StopCursorBlink()
1788 if ( mImpl->mCursorBlinkTimer )
1790 mImpl->mCursorBlinkTimer.Stop();
1793 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1796 void Decorator::DelayCursorBlink()
1798 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1799 mImpl->mDelayCursorBlink = true;
1802 void Decorator::SetCursorBlinkInterval( float seconds )
1804 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1807 float Decorator::GetCursorBlinkInterval() const
1809 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1812 void Decorator::SetCursorBlinkDuration( float seconds )
1814 mImpl->mCursorBlinkDuration = seconds;
1817 float Decorator::GetCursorBlinkDuration() const
1819 return mImpl->mCursorBlinkDuration;
1822 void Decorator::SetCursorWidth( int width )
1824 mImpl->mCursorWidth = static_cast<float>( width );
1827 int Decorator::GetCursorWidth() const
1829 return static_cast<int>( mImpl->mCursorWidth );
1834 void Decorator::SetHandleActive( HandleType handleType, bool active )
1836 mImpl->mHandle[handleType].active = active;
1840 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1842 mImpl->mHandlePreviousCrossed = false;
1845 // TODO: this is a work-around.
1846 // The problem is the handle actor does not receive the touch event with the Interrupt
1847 // state when the power button is pressed and the application goes to background.
1848 mImpl->mHandle[handleType].pressed = false;
1849 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1850 ImageView imageView = mImpl->mHandle[handleType].actor;
1851 if( imageReleased && imageView )
1853 imageView.SetImage( imageReleased );
1859 bool Decorator::IsHandleActive( HandleType handleType ) const
1861 return mImpl->mHandle[handleType].active ;
1864 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1866 mImpl->SetHandleImage( handleType, handleImageType, image );
1869 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1871 return mImpl->mHandleImages[handleType][handleImageType];
1874 void Decorator::SetHandleColor( const Vector4& color )
1876 mImpl->mHandleColor = color;
1879 const Vector4& Decorator::GetHandleColor() const
1881 return mImpl->mHandleColor;
1884 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1886 // Adjust handle's displacement
1887 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1889 handle.grabDisplacementX -= x - handle.position.x;
1890 handle.grabDisplacementY -= y - handle.position.y;
1892 handle.position.x = x;
1893 handle.position.y = y;
1894 handle.lineHeight = height;
1897 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1899 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1901 x = handle.position.x;
1902 y = handle.position.y;
1903 height = handle.lineHeight;
1906 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1908 return mImpl->mHandle[handleType].position;
1911 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1913 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1916 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1918 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1921 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1923 mImpl->mFlipSelectionHandlesOnCross = enable;
1926 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1928 mImpl->mHandleCurrentCrossed = indicesSwapped;
1929 mImpl->mFlipLeftSelectionHandleDirection = left;
1930 mImpl->mFlipRightSelectionHandleDirection = right;
1933 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1935 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1938 void Decorator::ClearHighlights()
1940 mImpl->mHighlightQuadList.clear();
1941 mImpl->mHighlightPosition = Vector2::ZERO;
1944 void Decorator::SetHighlightColor( const Vector4& color )
1946 mImpl->mHighlightColor = color;
1949 const Vector4& Decorator::GetHighlightColor() const
1951 return mImpl->mHighlightColor;
1954 void Decorator::SetTextDepth( int textDepth )
1956 mImpl->mTextDepth = textDepth;
1959 void Decorator::SetPopupActive( bool active )
1961 mImpl->mActiveCopyPastePopup = active;
1964 bool Decorator::IsPopupActive() const
1966 return mImpl->mActiveCopyPastePopup ;
1969 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1971 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1973 if ( !mImpl->mCopyPastePopup.actor )
1975 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1976 #ifdef DECORATOR_DEBUG
1977 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1979 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1980 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1983 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1986 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1988 return mImpl->mEnabledPopupButtons;
1993 void Decorator::SetScrollThreshold( float threshold )
1995 mImpl->SetScrollThreshold( threshold );
1998 float Decorator::GetScrollThreshold() const
2000 return mImpl->GetScrollThreshold();
2003 void Decorator::SetScrollSpeed( float speed )
2005 mImpl->SetScrollSpeed( speed );
2008 float Decorator::GetScrollSpeed() const
2010 return mImpl->GetScrollSpeed();
2013 void Decorator::NotifyEndOfScroll()
2015 mImpl->NotifyEndOfScroll();
2018 Decorator::~Decorator()
2023 Decorator::Decorator( ControllerInterface& controller,
2024 TextSelectionPopupCallbackInterface& callbackInterface )
2027 mImpl = new Decorator::Impl( controller, callbackInterface );
2032 } // namespace Toolkit