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-data.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;
626 // Will consume tap gestures on handles.
627 mTapDetector = TapGestureDetector::New();
629 // Will consume double tap gestures on handles.
630 mTapDetector.SetMaximumTapsRequired( 2u );
632 // Will consume long press gestures on handles.
633 mLongPressDetector = LongPressGestureDetector::New();
635 // Detects pan gestures on handles.
636 mPanDetector = PanGestureDetector::New();
637 mPanDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
640 void CreateActiveLayer()
644 mActiveLayer = Layer::New();
645 #ifdef DECORATOR_DEBUG
646 mActiveLayer.SetName ( "ActiveLayerActor" );
649 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
650 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
652 // Add the active layer telling the controller it doesn't need clipping.
653 mController.AddDecoration( mActiveLayer, false );
656 mActiveLayer.RaiseToTop();
659 void SetSelectionHandleMarkerSize( HandleImpl& handle )
661 if( handle.markerActor )
663 handle.markerActor.SetSize( 0, handle.lineHeight );
667 void CreateGrabHandle()
669 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
670 if( !grabHandle.actor )
672 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
674 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
675 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
676 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
678 // Area that Grab handle responds to, larger than actual handle so easier to move
679 #ifdef DECORATOR_DEBUG
680 grabHandle.actor.SetName( "GrabHandleActor" );
681 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
683 grabHandle.grabArea = Control::New();
684 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
685 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
686 grabHandle.grabArea.SetName( "GrabArea" );
690 grabHandle.grabArea = Actor::New();
691 grabHandle.grabArea.SetName( "GrabArea" );
694 grabHandle.grabArea = Actor::New();
697 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
698 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
699 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
700 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
701 grabHandle.actor.Add( grabHandle.grabArea );
702 grabHandle.actor.SetColor( mHandleColor );
704 grabHandle.grabArea.TouchSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
706 // The grab handle's actor is attached to the tap and long press detectors in order to consume these events.
707 // Note that no callbacks are connected to any signal emitted by the tap and long press detectors.
708 mTapDetector.Attach( grabHandle.actor );
709 mLongPressDetector.Attach( grabHandle.actor );
711 // The grab handle's area is attached to the pan detector.
712 // The OnPan() method is connected to the signals emitted by the pan detector.
713 mPanDetector.Attach( grabHandle.grabArea );
715 mActiveLayer.Add( grabHandle.actor );
719 if( grabHandle.actor && !grabHandle.actor.GetParent() )
721 mActiveLayer.Add( grabHandle.actor );
725 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
729 handle.markerActor = ImageView::New( image );
730 handle.markerActor.SetColor( mHandleColor );
731 handle.actor.Add( handle.markerActor );
733 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
735 if( LEFT_SELECTION_HANDLE == handleType )
737 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
738 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
740 else if( RIGHT_SELECTION_HANDLE == handleType )
742 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
743 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
748 void CreateSelectionHandles()
750 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
753 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
755 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
756 #ifdef DECORATOR_DEBUG
757 primary.actor.SetName("SelectionHandleOne");
759 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
760 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
761 primary.actor.SetColor( mHandleColor );
763 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
764 #ifdef DECORATOR_DEBUG
765 primary.grabArea.SetName("SelectionHandleOneGrabArea");
767 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
768 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
769 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
770 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
772 primary.grabArea.TouchSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
774 // The handle's actor is attached to the tap and long press detectors in order to consume these events.
775 // Note that no callbacks are connected to any signal emitted by the tap and long press detectors.
776 mTapDetector.Attach( primary.actor );
777 mLongPressDetector.Attach( primary.actor );
779 // The handle's area is attached to the pan detector.
780 // The OnPan() method is connected to the signals emitted by the pan detector.
781 mPanDetector.Attach( primary.grabArea );
783 primary.actor.Add( primary.grabArea );
785 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
789 if( primary.actor && !primary.actor.GetParent() )
791 mActiveLayer.Add( primary.actor );
794 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
795 if( !secondary.actor )
797 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
799 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
800 #ifdef DECORATOR_DEBUG
801 secondary.actor.SetName("SelectionHandleTwo");
803 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
804 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
805 secondary.actor.SetColor( mHandleColor );
807 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
808 #ifdef DECORATOR_DEBUG
809 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
811 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
812 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
813 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
814 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
816 secondary.grabArea.TouchSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
818 // The handle's actor is attached to the tap and long press detectors in order to consume these events.
819 // Note that no callbacks are connected to any signal emitted by the tap and long press detectors.
820 mTapDetector.Attach( secondary.actor );
821 mLongPressDetector.Attach( secondary.actor );
823 // The handle's area is attached to the pan detector.
824 // The OnPan() method is connected to the signals emitted by the pan detector.
825 mPanDetector.Attach( secondary.grabArea );
827 secondary.actor.Add( secondary.grabArea );
829 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
833 if( secondary.actor && !secondary.actor.GetParent() )
835 mActiveLayer.Add( secondary.actor );
839 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
841 // Gets the world position of the active layer. The active layer is where the handles are added.
842 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
844 // The grab handle position in world coords.
845 // The active layer's world position is the center of the active layer. The origin of the
846 // coord system of the handles is the top left of the active layer.
847 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
848 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
851 void SetGrabHandlePosition()
853 // Reference to the grab handle.
854 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
856 // Transforms the handle position into world coordinates.
857 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
858 // as it's transforming the handle's position set by the text-controller and not
859 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
860 // retrieves the position of the center of the actor but the handle's position set
861 // by the text controller is not the center of the actor.
862 Vector2 grabHandleWorldPosition;
863 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
865 // Check if the grab handle exceeds the boundaries of the decoration box.
866 // At the moment only the height is checked for the grab handle.
868 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
869 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
870 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
872 // The grab handle 'y' position in local coords.
873 // If the grab handle exceeds the bottom of the decoration box,
874 // set the 'y' position to the top of the line.
875 // The SetGrabHandleImage() method will change the orientation.
876 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
878 if( grabHandle.actor )
880 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
881 yLocalPosition ); // TODO : Fix for multiline.
885 void SetSelectionHandlePosition( HandleType type )
887 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
889 // Reference to the selection handle.
890 HandleImpl& handle = mHandle[type];
892 // Transforms the handle position into world coordinates.
893 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
894 // as it's transforming the handle's position set by the text-controller and not
895 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
896 // retrieves the position of the center of the actor but the handle's position set
897 // by the text controller is not the center of the actor.
898 Vector2 handleWorldPosition;
899 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
901 // Whether to flip the handle (horizontally).
902 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
904 // Whether to flip the handles if they are crossed.
905 bool crossFlip = false;
906 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
908 crossFlip = mHandleCurrentCrossed;
911 // Does not flip if both conditions are true (double flip)
912 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
914 // Will flip the handles vertically if the user prefers it.
915 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
917 if( crossFlip || mHandlePreviousCrossed )
919 if( isPrimaryHandle )
921 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
925 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
929 // Check if the selection handle exceeds the boundaries of the decoration box.
930 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
931 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
933 // Does not flip if both conditions are true (double flip)
934 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
938 if( handle.actor && !handle.horizontallyFlipped )
940 // Change the anchor point to flip the image.
941 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
943 handle.horizontallyFlipped = true;
948 if( handle.actor && handle.horizontallyFlipped )
950 // Reset the anchor point.
951 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
953 handle.horizontallyFlipped = false;
957 // Whether to flip the handle vertically.
958 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
959 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
960 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
962 // The primary selection handle 'y' position in local coords.
963 // If the handle exceeds the bottom of the decoration box,
964 // set the 'y' position to the top of the line.
965 // The SetHandleImage() method will change the orientation.
966 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
970 handle.actor.SetPosition( handle.position.x,
971 yLocalPosition ); // TODO : Fix for multiline.
975 void SetHandleImage( HandleType type )
977 HandleImpl& handle = mHandle[type];
979 HandleType markerType = HANDLE_TYPE_COUNT;
980 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
981 if( LEFT_SELECTION_HANDLE == type )
983 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
984 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
986 else if( RIGHT_SELECTION_HANDLE == type )
988 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
989 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
992 // Chooses between the released or pressed image. It checks whether the pressed image exists.
995 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
997 handle.actor.SetImage( mHandleImages[type][imageType] );
1000 if( HANDLE_TYPE_COUNT != markerType )
1002 if( handle.markerActor )
1004 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
1005 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
1009 // Whether to flip the handle vertically.
1012 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
1016 void CreateHighlight()
1018 if( !mHighlightActor )
1020 mHighlightActor = Actor::New();
1022 #ifdef DECORATOR_DEBUG
1023 mHighlightActor.SetName( "HighlightActor" );
1025 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
1026 mHighlightActor.SetSize( 1.0f, 1.0f );
1027 mHighlightActor.SetColor( mHighlightColor );
1028 mHighlightActor.SetColorMode( USE_OWN_COLOR );
1031 // Add the highlight box telling the controller it needs clipping.
1032 mController.AddDecoration( mHighlightActor, true );
1035 void UpdateHighlight()
1037 if ( mHighlightActor )
1039 if( !mHighlightQuadList.empty() )
1041 Vector< Vector2 > vertices;
1042 Vector< unsigned short> indices;
1045 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1046 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1048 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1050 QuadCoordinates& quad = *iter;
1053 vertex.x = quad.min.x;
1054 vertex.y = quad.min.y;
1055 vertices.PushBack( vertex );
1058 vertex.x = quad.max.x;
1059 vertex.y = quad.min.y;
1060 vertices.PushBack( vertex );
1062 // bottom-left (v+2)
1063 vertex.x = quad.min.x;
1064 vertex.y = quad.max.y;
1065 vertices.PushBack( vertex );
1067 // bottom-right (v+3)
1068 vertex.x = quad.max.x;
1069 vertex.y = quad.max.y;
1070 vertices.PushBack( vertex );
1072 // triangle A (3, 1, 0)
1073 indices.PushBack( v + 3 );
1074 indices.PushBack( v + 1 );
1075 indices.PushBack( v );
1077 // triangle B (0, 2, 3)
1078 indices.PushBack( v );
1079 indices.PushBack( v + 2 );
1080 indices.PushBack( v + 3 );
1083 if( ! mQuadVertices )
1085 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1088 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1090 if( !mQuadGeometry )
1092 mQuadGeometry = Geometry::New();
1093 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1095 mQuadGeometry.SetIndexBuffer( &indices[ 0 ], indices.Size() );
1097 if( !mHighlightRenderer )
1099 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightShader );
1100 mHighlightActor.AddRenderer( mHighlightRenderer );
1104 mHighlightActor.SetPosition( mHighlightPosition.x,
1105 mHighlightPosition.y );
1107 mHighlightQuadList.clear();
1109 if( mHighlightRenderer )
1111 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1116 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1118 if( Gesture::Started == gesture.state )
1120 handle.grabDisplacementX = handle.grabDisplacementY = 0.f;
1123 handle.grabDisplacementX += gesture.displacement.x;
1124 handle.grabDisplacementY += ( handle.verticallyFlipped ? -gesture.displacement.y : gesture.displacement.y );
1126 const float x = handle.position.x + handle.grabDisplacementX;
1127 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1129 if( Gesture::Started == gesture.state ||
1130 Gesture::Continuing == gesture.state )
1133 mController.GetTargetSize( targetSize );
1135 if( x < mScrollThreshold )
1137 mScrollDirection = SCROLL_RIGHT;
1138 mHandleScrolling = type;
1141 else if( x > targetSize.width - mScrollThreshold )
1143 mScrollDirection = SCROLL_LEFT;
1144 mHandleScrolling = type;
1149 mHandleScrolling = HANDLE_TYPE_COUNT;
1151 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1154 mHandlePanning = true;
1156 else if( Gesture::Finished == gesture.state ||
1157 Gesture::Cancelled == gesture.state )
1160 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1162 mNotifyEndOfScroll = false;
1163 mHandleScrolling = HANDLE_TYPE_COUNT;
1165 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1169 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1174 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1176 handle.pressed = false;
1178 mHandlePanning = false;
1182 void OnPan( Actor actor, const PanGesture& gesture )
1184 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1185 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1186 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1188 if( actor == grabHandle.grabArea )
1190 DoPan( grabHandle, GRAB_HANDLE, gesture );
1192 else if( actor == primarySelectionHandle.grabArea )
1194 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1196 else if( actor == secondarySelectionHandle.grabArea )
1198 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1202 bool OnGrabHandleTouched( Actor actor, const TouchData& touch )
1204 // Switch between pressed/release grab-handle images
1205 if( touch.GetPointCount() > 0 &&
1206 mHandle[GRAB_HANDLE].actor )
1208 const PointState::Type state = touch.GetState( 0 );
1210 if( PointState::DOWN == state )
1212 mHandle[GRAB_HANDLE].pressed = true;
1214 else if( ( PointState::UP == state ) ||
1215 ( PointState::INTERRUPTED == state ) )
1217 mHandle[GRAB_HANDLE].pressed = false;
1220 SetHandleImage( GRAB_HANDLE );
1223 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1227 bool OnHandleOneTouched( Actor actor, const TouchData& touch )
1229 // Switch between pressed/release selection handle images
1230 if( touch.GetPointCount() > 0 &&
1231 mHandle[LEFT_SELECTION_HANDLE].actor )
1233 const PointState::Type state = touch.GetState( 0 );
1235 if( PointState::DOWN == state )
1237 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1239 else if( ( PointState::UP == state ) ||
1240 ( PointState::INTERRUPTED == state ) )
1242 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1243 mHandlePreviousCrossed = mHandleCurrentCrossed;
1244 mHandlePanning = false;
1247 SetHandleImage( LEFT_SELECTION_HANDLE );
1250 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1254 bool OnHandleTwoTouched( Actor actor, const TouchData& touch )
1256 // Switch between pressed/release selection handle images
1257 if( touch.GetPointCount() > 0 &&
1258 mHandle[RIGHT_SELECTION_HANDLE].actor )
1260 const PointState::Type state = touch.GetState( 0 );
1262 if( PointState::DOWN == state )
1264 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1266 else if( ( PointState::UP == state ) ||
1267 ( PointState::INTERRUPTED == state ) )
1269 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1270 mHandlePreviousCrossed = mHandleCurrentCrossed;
1271 mHandlePanning = false;
1274 SetHandleImage( RIGHT_SELECTION_HANDLE );
1277 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1281 void HandleResetPosition( PropertyNotification& source )
1283 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1285 if( grabHandle.active )
1287 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1288 SetGrabHandlePosition();
1290 // Sets the grab handle image according if it's pressed, flipped, etc.
1291 SetHandleImage( GRAB_HANDLE );
1295 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1296 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1298 // Sets the primary handle image according if it's pressed, flipped, etc.
1299 SetHandleImage( LEFT_SELECTION_HANDLE );
1301 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1302 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1304 // Sets the secondary handle image according if it's pressed, flipped, etc.
1305 SetHandleImage( RIGHT_SELECTION_HANDLE );
1309 void SetupActiveLayerPropertyNotifications()
1316 // Vertical notifications.
1318 // Disconnect any previous connected callback.
1319 if( mVerticalLessThanNotification )
1321 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1322 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1325 if( mVerticalGreaterThanNotification )
1327 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1328 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1331 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1332 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1333 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1335 if( grabHandle.active )
1337 if( grabHandle.verticallyFlipped )
1339 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1340 mVerticalGreaterThanNotification.Reset();
1342 // The vertical distance from the center of the active layer to the top edje of the display.
1343 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1345 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1346 LessThanCondition( mBoundingBox.y + topHeight ) );
1348 // Notifies the change from false to true and from true to false.
1349 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1351 // Connects the signals with the callbacks.
1352 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1356 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1357 mVerticalLessThanNotification.Reset();
1359 // The vertical distance from the center of the active layer to the bottom edje of the display.
1360 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1362 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1363 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1365 // Notifies the change from false to true and from true to false.
1366 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1368 // Connects the signals with the callbacks.
1369 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1372 else // The selection handles are active
1374 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1376 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1377 mVerticalGreaterThanNotification.Reset();
1379 // The vertical distance from the center of the active layer to the top edje of the display.
1380 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1382 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1383 LessThanCondition( mBoundingBox.y + topHeight ) );
1385 // Notifies the change from false to true and from true to false.
1386 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1388 // Connects the signals with the callbacks.
1389 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1391 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1393 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1394 mVerticalLessThanNotification.Reset();
1396 // The vertical distance from the center of the active layer to the bottom edje of the display.
1397 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1398 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1400 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1401 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1403 // Notifies the change from false to true and from true to false.
1404 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1406 // Connects the signals with the callbacks.
1407 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1411 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1413 // The vertical distance from the center of the active layer to the top edje of the display.
1414 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1415 -primaryHandle.position.y + primaryHandle.size.height :
1416 -secondaryHandle.position.y + secondaryHandle.size.height );
1418 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1419 LessThanCondition( mBoundingBox.y + topHeight ) );
1421 // Notifies the change from false to true and from true to false.
1422 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1424 // Connects the signals with the callbacks.
1425 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1427 // The vertical distance from the center of the active layer to the bottom edje of the display.
1428 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1429 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1430 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1432 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1433 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1435 // Notifies the change from false to true and from true to false.
1436 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1438 // Connects the signals with the callbacks.
1439 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1443 // Horizontal notifications.
1445 // Disconnect any previous connected callback.
1446 if( mHorizontalLessThanNotification )
1448 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1449 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1452 if( mHorizontalGreaterThanNotification )
1454 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1455 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1458 if( primaryHandle.active || secondaryHandle.active )
1460 // The horizontal distance from the center of the active layer to the left edje of the display.
1461 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1462 -secondaryHandle.position.x + secondaryHandle.size.width );
1464 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1465 LessThanCondition( mBoundingBox.x + leftWidth ) );
1467 // Notifies the change from false to true and from true to false.
1468 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1470 // Connects the signals with the callbacks.
1471 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1473 // The horizontal distance from the center of the active layer to the right edje of the display.
1474 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1475 secondaryHandle.position.x + secondaryHandle.size.width );
1477 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1478 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1480 // Notifies the change from false to true and from true to false.
1481 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1483 // Connects the signals with the callbacks.
1484 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1490 float AlternatePopUpPositionRelativeToCursor()
1492 float alternativePosition = 0.0f;
1494 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1496 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1497 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1498 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1499 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1501 if( primaryHandle.active || secondaryHandle.active )
1503 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1504 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1508 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1511 return alternativePosition;
1514 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1516 float alternativeYPosition = 0.0f;
1517 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1518 // if can't be positioned above, then position below row.
1519 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1521 mCopyPastePopup.actor.SetY( alternativeYPosition );
1524 void SetUpPopupPositionNotifications()
1526 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1528 // Exceeding vertical boundary
1530 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1532 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1533 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1534 mBoundingBox.w - popupHeight * 0.5f ) );
1536 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1539 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1541 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1543 // Parent must already by added to Stage for these Get calls to work
1544 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1545 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1547 // Calculate distance to move popup (in local space) so fits within the boundary
1548 float xOffSetToKeepWithinBounds = 0.0f;
1549 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1551 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1553 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1555 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1558 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1559 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1561 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1564 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1566 // Prevent pixel mis-alignment by rounding down.
1567 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1568 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1571 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1573 HandleImpl& handle = mHandle[handleType];
1574 handle.size = Size( image.GetWidth(), image.GetHeight() );
1576 mHandleImages[handleType][handleImageType] = image;
1579 void SetScrollThreshold( float threshold )
1581 mScrollThreshold = threshold;
1584 float GetScrollThreshold() const
1586 return mScrollThreshold;
1589 void SetScrollSpeed( float speed )
1591 mScrollSpeed = speed;
1592 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1595 float GetScrollSpeed() const
1597 return mScrollSpeed;
1600 void NotifyEndOfScroll()
1606 mNotifyEndOfScroll = true;
1611 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1613 * It only starts the timer if it's already created.
1615 void StartScrollTimer()
1619 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1620 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1623 if( !mScrollTimer.IsRunning() )
1625 mScrollTimer.Start();
1630 * Stops the timer used to scroll the text.
1632 void StopScrollTimer()
1636 mScrollTimer.Stop();
1641 * Callback called by the timer used to scroll the text.
1643 * It calculates and sets a new scroll position.
1645 bool OnScrollTimerTick()
1647 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1649 mController.DecorationEvent( mHandleScrolling,
1651 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1658 ControllerInterface& mController;
1660 TapGestureDetector mTapDetector;
1661 PanGestureDetector mPanDetector;
1662 LongPressGestureDetector mLongPressDetector;
1664 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1665 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1667 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1668 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1669 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1670 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1671 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1672 Control mPrimaryCursor;
1673 Control mSecondaryCursor;
1675 Actor mHighlightActor; ///< Actor to display highlight
1676 Renderer mHighlightRenderer;
1677 Shader mHighlightShader; ///< Shader used for highlight
1678 Property::Map mQuadVertexFormat;
1679 PopupImpl mCopyPastePopup;
1680 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1681 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1683 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1684 Vector4 mHandleColor;
1686 CursorImpl mCursor[CURSOR_COUNT];
1687 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1689 PropertyBuffer mQuadVertices;
1690 Geometry mQuadGeometry;
1691 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1693 Vector4 mBoundingBox; ///< The bounding box in world coords.
1694 Vector4 mHighlightColor; ///< Color of the highlight
1695 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1696 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1698 unsigned int mActiveCursor;
1699 unsigned int mCursorBlinkInterval;
1700 float mCursorBlinkDuration;
1701 float mCursorWidth; ///< The width of the cursors in pixels.
1702 HandleType mHandleScrolling; ///< The handle which is scrolling.
1703 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1704 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1705 float mScrollSpeed; ///< The scroll speed in pixels per second.
1706 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1707 int mTextDepth; ///< The depth used to render the text.
1709 bool mActiveCopyPastePopup : 1;
1710 bool mPopupSetNewPosition : 1;
1711 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1712 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1713 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1714 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1715 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1716 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1717 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1718 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1719 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1720 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1721 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1724 DecoratorPtr Decorator::New( ControllerInterface& controller,
1725 TextSelectionPopupCallbackInterface& callbackInterface )
1727 return DecoratorPtr( new Decorator( controller,
1728 callbackInterface ) );
1731 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1733 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1736 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1738 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1741 void Decorator::Relayout( const Vector2& size )
1743 mImpl->Relayout( size );
1746 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1748 mImpl->UpdatePositions( scrollOffset );
1753 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1755 mImpl->mActiveCursor = activeCursor;
1758 unsigned int Decorator::GetActiveCursor() const
1760 return mImpl->mActiveCursor;
1763 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1765 mImpl->mCursor[cursor].position.x = x;
1766 mImpl->mCursor[cursor].position.y = y;
1767 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1768 mImpl->mCursor[cursor].lineHeight = lineHeight;
1771 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1773 x = mImpl->mCursor[cursor].position.x;
1774 y = mImpl->mCursor[cursor].position.y;
1775 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1776 lineHeight = mImpl->mCursor[cursor].lineHeight;
1779 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1781 return mImpl->mCursor[cursor].position;
1784 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1786 mImpl->mCursor[cursor].color = color;
1789 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1791 return mImpl->mCursor[cursor].color;
1794 void Decorator::StartCursorBlink()
1796 if ( !mImpl->mCursorBlinkTimer )
1798 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1799 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1802 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1804 mImpl->mCursorBlinkTimer.Start();
1808 void Decorator::StopCursorBlink()
1810 if ( mImpl->mCursorBlinkTimer )
1812 mImpl->mCursorBlinkTimer.Stop();
1815 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1818 void Decorator::DelayCursorBlink()
1820 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1821 mImpl->mDelayCursorBlink = true;
1824 void Decorator::SetCursorBlinkInterval( float seconds )
1826 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1829 float Decorator::GetCursorBlinkInterval() const
1831 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1834 void Decorator::SetCursorBlinkDuration( float seconds )
1836 mImpl->mCursorBlinkDuration = seconds;
1839 float Decorator::GetCursorBlinkDuration() const
1841 return mImpl->mCursorBlinkDuration;
1844 void Decorator::SetCursorWidth( int width )
1846 mImpl->mCursorWidth = static_cast<float>( width );
1849 int Decorator::GetCursorWidth() const
1851 return static_cast<int>( mImpl->mCursorWidth );
1856 void Decorator::SetHandleActive( HandleType handleType, bool active )
1858 mImpl->mHandle[handleType].active = active;
1862 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1864 mImpl->mHandlePreviousCrossed = false;
1867 // TODO: this is a work-around.
1868 // The problem is the handle actor does not receive the touch event with the Interrupt
1869 // state when the power button is pressed and the application goes to background.
1870 mImpl->mHandle[handleType].pressed = false;
1871 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1872 ImageView imageView = mImpl->mHandle[handleType].actor;
1873 if( imageReleased && imageView )
1875 imageView.SetImage( imageReleased );
1881 bool Decorator::IsHandleActive( HandleType handleType ) const
1883 return mImpl->mHandle[handleType].active ;
1886 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1888 mImpl->SetHandleImage( handleType, handleImageType, image );
1891 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1893 return mImpl->mHandleImages[handleType][handleImageType];
1896 void Decorator::SetHandleColor( const Vector4& color )
1898 mImpl->mHandleColor = color;
1901 const Vector4& Decorator::GetHandleColor() const
1903 return mImpl->mHandleColor;
1906 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1908 // Adjust handle's displacement
1909 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1911 handle.grabDisplacementX -= x - handle.position.x;
1912 handle.grabDisplacementY -= y - handle.position.y;
1914 handle.position.x = x;
1915 handle.position.y = y;
1916 handle.lineHeight = height;
1919 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1921 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1923 x = handle.position.x;
1924 y = handle.position.y;
1925 height = handle.lineHeight;
1928 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1930 return mImpl->mHandle[handleType].position;
1933 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1935 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1938 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1940 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1943 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1945 mImpl->mFlipSelectionHandlesOnCross = enable;
1948 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1950 mImpl->mHandleCurrentCrossed = indicesSwapped;
1951 mImpl->mFlipLeftSelectionHandleDirection = left;
1952 mImpl->mFlipRightSelectionHandleDirection = right;
1955 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1957 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1960 void Decorator::ClearHighlights()
1962 mImpl->mHighlightQuadList.clear();
1963 mImpl->mHighlightPosition = Vector2::ZERO;
1966 void Decorator::SetHighlightColor( const Vector4& color )
1968 mImpl->mHighlightColor = color;
1971 const Vector4& Decorator::GetHighlightColor() const
1973 return mImpl->mHighlightColor;
1976 void Decorator::SetTextDepth( int textDepth )
1978 mImpl->mTextDepth = textDepth;
1981 void Decorator::SetPopupActive( bool active )
1983 mImpl->mActiveCopyPastePopup = active;
1986 bool Decorator::IsPopupActive() const
1988 return mImpl->mActiveCopyPastePopup ;
1991 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1993 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1995 if ( !mImpl->mCopyPastePopup.actor )
1997 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1998 #ifdef DECORATOR_DEBUG
1999 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
2001 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
2002 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
2005 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
2008 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
2010 return mImpl->mEnabledPopupButtons;
2015 void Decorator::SetScrollThreshold( float threshold )
2017 mImpl->SetScrollThreshold( threshold );
2020 float Decorator::GetScrollThreshold() const
2022 return mImpl->GetScrollThreshold();
2025 void Decorator::SetScrollSpeed( float speed )
2027 mImpl->SetScrollSpeed( speed );
2030 float Decorator::GetScrollSpeed() const
2032 return mImpl->GetScrollSpeed();
2035 void Decorator::NotifyEndOfScroll()
2037 mImpl->NotifyEndOfScroll();
2040 Decorator::~Decorator()
2045 Decorator::Decorator( ControllerInterface& controller,
2046 TextSelectionPopupCallbackInterface& callbackInterface )
2049 mImpl = new Decorator::Impl( controller, callbackInterface );
2054 } // namespace Toolkit