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/default-controls/solid-color-actor.h>
37 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
38 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
39 #include <dali-toolkit/internal/controls/image-view/image-view-impl.h>
42 #define DECORATOR_DEBUG
46 #define MAKE_SHADER(A)#A
50 const char* VERTEX_SHADER = MAKE_SHADER(
51 attribute mediump vec2 aPosition;
52 uniform mediump mat4 uMvpMatrix;
53 uniform mediump vec3 uSize;
57 mediump vec4 position = vec4( aPosition, 0.0, 1.0 );
58 position.xyz *= uSize;
59 gl_Position = uMvpMatrix * position;
63 const char* FRAGMENT_SHADER = MAKE_SHADER(
64 uniform lowp vec4 uColor;
68 gl_FragColor = uColor;
79 #ifdef DECORATOR_DEBUG
80 Integration::Log::Filter* gLogFilter( Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_TEXT_DECORATOR") );
90 const Dali::Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
91 const Dali::Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.25f, 1.5f, 1.0f );
93 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.
95 const Dali::Vector4 HANDLE_COLOR( 0.0f, (183.0f / 255.0f), (229.0f / 255.0f), 1.0f );
97 const unsigned int CURSOR_BLINK_INTERVAL = 500u; // Cursor blink interval
98 const float TO_MILLISECONDS = 1000.f;
99 const float TO_SECONDS = 1.f / TO_MILLISECONDS;
101 const unsigned int SCROLL_TICK_INTERVAL = 50u;
103 const float SCROLL_THRESHOLD = 10.f;
104 const float SCROLL_SPEED = 300.f;
105 const float SCROLL_DISTANCE = SCROLL_SPEED * SCROLL_TICK_INTERVAL * TO_SECONDS;
107 const float CURSOR_WIDTH = 1.f;
110 * structure to hold coordinates of each quad, which will make up the mesh.
112 struct QuadCoordinates
115 * Default constructor
123 * @param[in] x1 left co-ordinate
124 * @param[in] y1 top co-ordinate
125 * @param[in] x2 right co-ordinate
126 * @param[in] y2 bottom co-ordinate
128 QuadCoordinates(float x1, float y1, float x2, float y2)
134 Dali::Vector2 min; ///< top-left (minimum) position of quad
135 Dali::Vector2 max; ///< bottom-right (maximum) position of quad
138 typedef std::vector<QuadCoordinates> QuadContainer;
141 * @brief Takes a bounding rectangle in the local coordinates of an actor and returns the world coordinates Bounding Box.
142 * @param[in] boundingRectangle local bounding
143 * @param[out] Vector4 World coordinate bounding Box.
145 void LocalToWorldCoordinatesBoundingBox( const Dali::Rect<int>& boundingRectangle, Dali::Vector4& boundingBox )
147 // Convert to world coordinates and store as a Vector4 to be compatible with Property Notifications.
148 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
150 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
151 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
153 boundingBox = Dali::Vector4( originX,
155 originX + boundingRectangle.width,
156 originY + boundingRectangle.height );
159 void WorldToLocalCoordinatesBoundingBox( const Dali::Vector4& boundingBox, Dali::Rect<int>& boundingRectangle )
161 // Convert to local coordinates and store as a Dali::Rect.
162 Dali::Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
164 boundingRectangle.x = boundingBox.x + 0.5f * stageSize.width;
165 boundingRectangle.y = boundingBox.y + 0.5f * stageSize.height;
166 boundingRectangle.width = boundingBox.z - boundingBox.x;
167 boundingRectangle.height = boundingBox.w - boundingBox.y;
170 } // end of namespace
181 struct Decorator::Impl : public ConnectionTracker
195 : color( Dali::Color::BLACK ),
197 cursorHeight( 0.0f ),
214 grabDisplacementX( 0.f ),
215 grabDisplacementY( 0.f ),
219 verticallyFlippedPreferred( false ),
220 horizontallyFlipped( false ),
221 verticallyFlipped( false )
227 ImageView markerActor;
231 float lineHeight; ///< Not the handle height
232 float grabDisplacementX;
233 float grabDisplacementY;
237 bool verticallyFlippedPreferred : 1; ///< Whether the handle is preferred to be vertically flipped.
238 bool horizontallyFlipped : 1; ///< Whether the handle has been horizontally flipped.
239 bool verticallyFlipped : 1; ///< Whether the handle has been vertically flipped.
249 TextSelectionPopup actor;
253 Impl( ControllerInterface& controller,
254 TextSelectionPopupCallbackInterface& callbackInterface )
255 : mController( controller ),
256 mEnabledPopupButtons( TextSelectionPopup::NONE ),
257 mTextSelectionPopupCallbackInterface( callbackInterface ),
258 mHandleColor( HANDLE_COLOR ),
260 mHighlightColor( LIGHT_BLUE ),
261 mHighlightPosition( Vector2::ZERO ),
262 mActiveCursor( ACTIVE_CURSOR_NONE ),
263 mCursorBlinkInterval( CURSOR_BLINK_INTERVAL ),
264 mCursorBlinkDuration( 0.0f ),
265 mCursorWidth( CURSOR_WIDTH ),
266 mHandleScrolling( HANDLE_TYPE_COUNT ),
267 mScrollDirection( SCROLL_NONE ),
268 mScrollThreshold( SCROLL_THRESHOLD ),
269 mScrollSpeed( SCROLL_SPEED ),
270 mScrollDistance( SCROLL_DISTANCE ),
272 mActiveCopyPastePopup( false ),
273 mPopupSetNewPosition( true ),
274 mCursorBlinkStatus( true ),
275 mDelayCursorBlink( false ),
276 mPrimaryCursorVisible( false ),
277 mSecondaryCursorVisible( false ),
278 mFlipSelectionHandlesOnCross( false ),
279 mFlipLeftSelectionHandleDirection( false ),
280 mFlipRightSelectionHandleDirection( false ),
281 mHandlePanning( false ),
282 mHandleCurrentCrossed( false ),
283 mHandlePreviousCrossed( false ),
284 mNotifyEndOfScroll( false )
286 mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2;
287 mQuadIndexFormat[ "indices" ] = Property::INTEGER;
288 mHighlightMaterial = Material::New( Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ) );
294 * Relayout of the decorations owned by the decorator.
295 * @param[in] size The Size of the UI control the decorator is adding it's decorations to.
297 void Relayout( const Vector2& size )
301 // TODO - Remove this if nothing is active
304 // Show or hide the cursors
309 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
310 mPrimaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
311 if( mPrimaryCursorVisible )
313 mPrimaryCursor.SetPosition( cursor.position.x,
315 mPrimaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
317 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
319 if( mSecondaryCursor )
321 const CursorImpl& cursor = mCursor[SECONDARY_CURSOR];
322 mSecondaryCursorVisible = ( cursor.position.x + mCursorWidth <= mControlSize.width ) && ( cursor.position.x >= 0.f );
323 if( mSecondaryCursorVisible )
325 mSecondaryCursor.SetPosition( cursor.position.x,
327 mSecondaryCursor.SetSize( Size( mCursorWidth, cursor.cursorHeight ) );
329 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
332 // Show or hide the grab handle
333 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
334 bool newGrabHandlePosition = false;
335 if( grabHandle.active )
337 const bool isVisible = ( grabHandle.position.x + floor( 0.5f * mCursorWidth ) <= mControlSize.width ) && ( grabHandle.position.x >= 0.f );
343 // Sets the grab handle position and calculate if it needs to be vertically flipped if it exceeds the boundary box.
344 SetGrabHandlePosition();
346 // Sets the grab handle image according if it's pressed, flipped, etc.
347 SetHandleImage( GRAB_HANDLE );
349 newGrabHandlePosition = true;
352 if( grabHandle.actor )
354 grabHandle.actor.SetVisible( isVisible );
357 else if( grabHandle.actor )
359 grabHandle.actor.Unparent();
362 // Show or hide the selection handles/highlight
363 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
364 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
365 bool newPrimaryHandlePosition = false;
366 bool newSecondaryHandlePosition = false;
367 if( primary.active || secondary.active )
369 const bool isPrimaryVisible = ( primary.position.x <= mControlSize.width ) && ( primary.position.x >= 0.f );
370 const bool isSecondaryVisible = ( secondary.position.x <= mControlSize.width ) && ( secondary.position.x >= 0.f );
372 if( isPrimaryVisible || isSecondaryVisible )
374 CreateSelectionHandles();
376 if( isPrimaryVisible )
378 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
380 // Sets the primary handle image according if it's pressed, flipped, etc.
381 SetHandleImage( LEFT_SELECTION_HANDLE );
383 SetSelectionHandleMarkerSize( primary );
385 newPrimaryHandlePosition = true;
388 if( isSecondaryVisible )
390 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
392 // Sets the secondary handle image according if it's pressed, flipped, etc.
393 SetHandleImage( RIGHT_SELECTION_HANDLE );
395 SetSelectionHandleMarkerSize( secondary );
397 newSecondaryHandlePosition = true;
403 primary.actor.SetVisible( isPrimaryVisible );
405 if( secondary.actor )
407 secondary.actor.SetVisible( isSecondaryVisible );
417 primary.actor.Unparent();
419 if( secondary.actor )
421 secondary.actor.Unparent();
423 if( mHighlightActor )
425 mHighlightActor.Unparent();
429 if( newGrabHandlePosition ||
430 newPrimaryHandlePosition ||
431 newSecondaryHandlePosition )
433 // Setup property notifications to find whether the handles leave the boundaries of the current display.
434 SetupActiveLayerPropertyNotifications();
437 if( mActiveCopyPastePopup )
440 mPopupSetNewPosition = true;
444 if( mCopyPastePopup.actor )
446 mCopyPastePopup.actor.HidePopup();
447 mPopupSetNewPosition = true;
452 void UpdatePositions( const Vector2& scrollOffset )
454 mCursor[PRIMARY_CURSOR].position += scrollOffset;
455 mCursor[SECONDARY_CURSOR].position += scrollOffset;
456 mHandle[ GRAB_HANDLE ].position += scrollOffset;
457 mHandle[ LEFT_SELECTION_HANDLE ].position += scrollOffset;
458 mHandle[ RIGHT_SELECTION_HANDLE ].position += scrollOffset;
459 mHighlightPosition += scrollOffset;
464 if ( !mCopyPastePopup.actor )
469 if( !mCopyPastePopup.actor.GetParent() )
471 mActiveLayer.Add( mCopyPastePopup.actor );
474 mCopyPastePopup.actor.RaiseAbove( mActiveLayer );
475 mCopyPastePopup.actor.ShowPopup();
478 void DeterminePositionPopup()
480 if( !mActiveCopyPastePopup )
485 // Retrieves the popup's size after relayout.
486 const Vector3 popupSize = Vector3( mCopyPastePopup.actor.GetRelayoutSize( Dimension::WIDTH ), mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT ), 0.0f );
488 if( mPopupSetNewPosition )
490 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
491 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
492 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
493 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
495 if( primaryHandle.active || secondaryHandle.active )
497 // Calculates the popup's position if selection handles are active.
498 const float minHandleXPosition = std::min( primaryHandle.position.x, secondaryHandle.position.x );
499 const float maxHandleXPosition = std::max( primaryHandle.position.x, secondaryHandle.position.x );
500 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
502 mCopyPastePopup.position.x = minHandleXPosition + ( ( maxHandleXPosition - minHandleXPosition ) * 0.5f );
503 mCopyPastePopup.position.y = -0.5f * popupSize.height - maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
507 // Calculates the popup's position if the grab handle is active.
508 mCopyPastePopup.position = Vector3( cursor.position.x, -0.5f * popupSize.height - grabHandle.size.height + cursor.position.y, 0.0f );
512 // Checks if there is enough space above the text control. If not it places the popup under it.
513 GetConstrainedPopupPosition( mCopyPastePopup.position, popupSize * AnchorPoint::CENTER, mActiveLayer, mBoundingBox );
515 SetUpPopupPositionNotifications();
517 mCopyPastePopup.actor.SetPosition( mCopyPastePopup.position );
518 mPopupSetNewPosition = false;
521 void PopupRelayoutComplete( Actor actor )
523 // Size negotiation for CopyPastePopup complete so can get the size and constrain position within bounding box.
525 DeterminePositionPopup();
528 void CreateCursor( Control& cursor, const Vector4& color )
530 cursor = Control::New();
531 cursor.SetBackgroundColor( color );
532 cursor.SetParentOrigin( ParentOrigin::TOP_LEFT ); // Need to set the default parent origin as CreateSolidColorActor() sets a different one.
533 cursor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
536 // Add or Remove cursor(s) from parent
539 if( mActiveCursor == ACTIVE_CURSOR_NONE )
543 mPrimaryCursor.Unparent();
545 if( mSecondaryCursor )
547 mSecondaryCursor.Unparent();
552 // Create Primary and or Secondary Cursor(s) if active and add to parent
553 if ( mActiveCursor == ACTIVE_CURSOR_PRIMARY ||
554 mActiveCursor == ACTIVE_CURSOR_BOTH )
556 if ( !mPrimaryCursor )
558 CreateCursor( mPrimaryCursor, mCursor[PRIMARY_CURSOR].color );
559 #ifdef DECORATOR_DEBUG
560 mPrimaryCursor.SetName( "PrimaryCursorActor" );
564 if( !mPrimaryCursor.GetParent() )
566 mActiveLayer.Add( mPrimaryCursor );
570 if ( mActiveCursor == ACTIVE_CURSOR_BOTH )
572 if ( !mSecondaryCursor )
574 CreateCursor( mSecondaryCursor, mCursor[SECONDARY_CURSOR].color );
575 #ifdef DECORATOR_DEBUG
576 mSecondaryCursor.SetName( "SecondaryCursorActor" );
580 if( !mSecondaryCursor.GetParent() )
582 mActiveLayer.Add( mSecondaryCursor );
587 if( mSecondaryCursor )
589 mSecondaryCursor.Unparent();
595 bool OnCursorBlinkTimerTick()
597 if( !mDelayCursorBlink )
600 if ( mPrimaryCursor )
602 mPrimaryCursor.SetVisible( mPrimaryCursorVisible && mCursorBlinkStatus );
604 if ( mSecondaryCursor )
606 mSecondaryCursor.SetVisible( mSecondaryCursorVisible && mCursorBlinkStatus );
609 mCursorBlinkStatus = !mCursorBlinkStatus;
614 mDelayCursorBlink = false;
620 void SetupTouchEvents()
622 mTapDetector = TapGestureDetector::New();
623 mTapDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnTap );
625 mPanGestureDetector = PanGestureDetector::New();
626 mPanGestureDetector.DetectedSignal().Connect( this, &Decorator::Impl::OnPan );
629 void CreateActiveLayer()
633 mActiveLayer = Layer::New();
634 #ifdef DECORATOR_DEBUG
635 mActiveLayer.SetName ( "ActiveLayerActor" );
638 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER );
639 mActiveLayer.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
641 // Add the active layer telling the controller it doesn't need clipping.
642 mController.AddDecoration( mActiveLayer, false );
645 mActiveLayer.RaiseToTop();
648 void SetSelectionHandleMarkerSize( HandleImpl& handle )
650 if( handle.markerActor )
652 handle.markerActor.SetSize( 0, handle.lineHeight );
656 void CreateGrabHandle()
658 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
659 if( !grabHandle.actor )
661 if( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] )
663 grabHandle.actor = ImageView::New( mHandleImages[GRAB_HANDLE][HANDLE_IMAGE_RELEASED] );
664 GetImpl( grabHandle.actor).SetDepthIndex( DepthIndex::DECORATION );
665 grabHandle.actor.SetAnchorPoint( AnchorPoint::TOP_CENTER );
667 // Area that Grab handle responds to, larger than actual handle so easier to move
668 #ifdef DECORATOR_DEBUG
669 grabHandle.actor.SetName( "GrabHandleActor" );
670 if ( Dali::Internal::gLogFilter->IsEnabledFor( Debug::Verbose ) )
672 grabHandle.grabArea = Control::New();
673 Toolkit::Control control = Toolkit::Control::DownCast( grabHandle.grabArea );
674 control.SetBackgroundColor( Vector4( 1.0f, 1.0f, 1.0f, 0.5f ) );
675 grabHandle.grabArea.SetName( "GrabArea" );
679 grabHandle.grabArea = Actor::New();
680 grabHandle.grabArea.SetName( "GrabArea" );
683 grabHandle.grabArea = Actor::New();
686 grabHandle.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
687 grabHandle.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
688 grabHandle.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
689 grabHandle.grabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
690 grabHandle.actor.Add( grabHandle.grabArea );
691 grabHandle.actor.SetColor( mHandleColor );
693 grabHandle.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnGrabHandleTouched );
694 mTapDetector.Attach( grabHandle.grabArea );
695 mPanGestureDetector.Attach( grabHandle.grabArea );
697 mActiveLayer.Add( grabHandle.actor );
701 if( grabHandle.actor && !grabHandle.actor.GetParent() )
703 mActiveLayer.Add( grabHandle.actor );
707 void CreateHandleMarker( HandleImpl& handle, Image& image, HandleType handleType )
711 handle.markerActor = ImageView::New( image );
712 handle.markerActor.SetColor( mHandleColor );
713 handle.actor.Add( handle.markerActor );
715 handle.markerActor.SetResizePolicy ( ResizePolicy::FIXED, Dimension::HEIGHT );
717 if( LEFT_SELECTION_HANDLE == handleType )
719 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_RIGHT );
720 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_RIGHT );
722 else if( RIGHT_SELECTION_HANDLE == handleType )
724 handle.markerActor.SetAnchorPoint( AnchorPoint::BOTTOM_LEFT );
725 handle.markerActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
730 void CreateSelectionHandles()
732 HandleImpl& primary = mHandle[ LEFT_SELECTION_HANDLE ];
735 if( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
737 primary.actor = ImageView::New( mHandleImages[LEFT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
738 #ifdef DECORATOR_DEBUG
739 primary.actor.SetName("SelectionHandleOne");
741 primary.actor.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
742 GetImpl( primary.actor ).SetDepthIndex( DepthIndex::DECORATION );
743 primary.actor.SetColor( mHandleColor );
745 primary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
746 #ifdef DECORATOR_DEBUG
747 primary.grabArea.SetName("SelectionHandleOneGrabArea");
749 primary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
750 primary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
751 primary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
752 primary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
754 mTapDetector.Attach( primary.grabArea );
755 mPanGestureDetector.Attach( primary.grabArea );
756 primary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleOneTouched );
758 primary.actor.Add( primary.grabArea );
760 CreateHandleMarker( primary, mHandleImages[LEFT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], LEFT_SELECTION_HANDLE );
764 if( primary.actor && !primary.actor.GetParent() )
766 mActiveLayer.Add( primary.actor );
769 HandleImpl& secondary = mHandle[ RIGHT_SELECTION_HANDLE ];
770 if( !secondary.actor )
772 if( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] )
774 secondary.actor = ImageView::New( mHandleImages[RIGHT_SELECTION_HANDLE][HANDLE_IMAGE_RELEASED] );
775 #ifdef DECORATOR_DEBUG
776 secondary.actor.SetName("SelectionHandleTwo");
778 secondary.actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); // Change to BOTTOM_LEFT if Look'n'Feel requires handle above text.
779 GetImpl( secondary.actor ).SetDepthIndex( DepthIndex::DECORATION );
780 secondary.actor.SetColor( mHandleColor );
782 secondary.grabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
783 #ifdef DECORATOR_DEBUG
784 secondary.grabArea.SetName("SelectionHandleTwoGrabArea");
786 secondary.grabArea.SetResizePolicy( ResizePolicy::SIZE_RELATIVE_TO_PARENT, Dimension::ALL_DIMENSIONS );
787 secondary.grabArea.SetParentOrigin( ParentOrigin::TOP_CENTER );
788 secondary.grabArea.SetAnchorPoint( AnchorPoint::TOP_CENTER );
789 secondary.grabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
791 mTapDetector.Attach( secondary.grabArea );
792 mPanGestureDetector.Attach( secondary.grabArea );
793 secondary.grabArea.TouchedSignal().Connect( this, &Decorator::Impl::OnHandleTwoTouched );
795 secondary.actor.Add( secondary.grabArea );
797 CreateHandleMarker( secondary, mHandleImages[RIGHT_SELECTION_HANDLE_MARKER][HANDLE_IMAGE_RELEASED], RIGHT_SELECTION_HANDLE );
801 if( secondary.actor && !secondary.actor.GetParent() )
803 mActiveLayer.Add( secondary.actor );
807 void CalculateHandleWorldCoordinates( HandleImpl& handle, Vector2& position )
809 // Gets the world position of the active layer. The active layer is where the handles are added.
810 const Vector3 parentWorldPosition = mActiveLayer.GetCurrentWorldPosition();
812 // The grab handle position in world coords.
813 // The active layer's world position is the center of the active layer. The origin of the
814 // coord system of the handles is the top left of the active layer.
815 position.x = parentWorldPosition.x - 0.5f * mControlSize.width + handle.position.x;
816 position.y = parentWorldPosition.y - 0.5f * mControlSize.height + handle.position.y;
819 void SetGrabHandlePosition()
821 // Reference to the grab handle.
822 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
824 // Transforms the handle position into world coordinates.
825 // @note This is not the same value as grabHandle.actor.GetCurrentWorldPosition()
826 // as it's transforming the handle's position set by the text-controller and not
827 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
828 // retrieves the position of the center of the actor but the handle's position set
829 // by the text controller is not the center of the actor.
830 Vector2 grabHandleWorldPosition;
831 CalculateHandleWorldCoordinates( grabHandle, grabHandleWorldPosition );
833 // Check if the grab handle exceeds the boundaries of the decoration box.
834 // At the moment only the height is checked for the grab handle.
836 grabHandle.verticallyFlipped = ( grabHandle.verticallyFlippedPreferred &&
837 ( ( grabHandleWorldPosition.y - grabHandle.size.height ) > mBoundingBox.y ) ) ||
838 ( grabHandleWorldPosition.y + grabHandle.lineHeight + grabHandle.size.height > mBoundingBox.w );
840 // The grab handle 'y' position in local coords.
841 // If the grab handle exceeds the bottom of the decoration box,
842 // set the 'y' position to the top of the line.
843 // The SetGrabHandleImage() method will change the orientation.
844 const float yLocalPosition = grabHandle.verticallyFlipped ? grabHandle.position.y : grabHandle.position.y + grabHandle.lineHeight;
846 if( grabHandle.actor )
848 grabHandle.actor.SetPosition( grabHandle.position.x + floor( 0.5f * mCursorWidth ),
849 yLocalPosition ); // TODO : Fix for multiline.
853 void SetSelectionHandlePosition( HandleType type )
855 const bool isPrimaryHandle = LEFT_SELECTION_HANDLE == type;
857 // Reference to the selection handle.
858 HandleImpl& handle = mHandle[type];
860 // Transforms the handle position into world coordinates.
861 // @note This is not the same value as handle.actor.GetCurrentWorldPosition()
862 // as it's transforming the handle's position set by the text-controller and not
863 // the final position set to the actor. Another difference is the GetCurrentWorldPosition()
864 // retrieves the position of the center of the actor but the handle's position set
865 // by the text controller is not the center of the actor.
866 Vector2 handleWorldPosition;
867 CalculateHandleWorldCoordinates( handle, handleWorldPosition );
869 // Whether to flip the handle (horizontally).
870 bool flipHandle = isPrimaryHandle ? mFlipLeftSelectionHandleDirection : mFlipRightSelectionHandleDirection;
872 // Whether to flip the handles if they are crossed.
873 bool crossFlip = false;
874 if( mFlipSelectionHandlesOnCross || !mHandlePanning )
876 crossFlip = mHandleCurrentCrossed;
879 // Does not flip if both conditions are true (double flip)
880 flipHandle = flipHandle != ( crossFlip || mHandlePreviousCrossed );
882 // Will flip the handles vertically if the user prefers it.
883 bool verticallyFlippedPreferred = handle.verticallyFlippedPreferred;
885 if( crossFlip || mHandlePreviousCrossed )
887 if( isPrimaryHandle )
889 verticallyFlippedPreferred = mHandle[RIGHT_SELECTION_HANDLE].verticallyFlippedPreferred;
893 verticallyFlippedPreferred = mHandle[LEFT_SELECTION_HANDLE].verticallyFlippedPreferred;
897 // Check if the selection handle exceeds the boundaries of the decoration box.
898 const bool exceedsLeftEdge = ( isPrimaryHandle ? !flipHandle : flipHandle ) && ( handleWorldPosition.x - handle.size.width < mBoundingBox.x );
899 const bool exceedsRightEdge = ( isPrimaryHandle ? flipHandle : !flipHandle ) && ( handleWorldPosition.x + handle.size.width > mBoundingBox.z );
901 // Does not flip if both conditions are true (double flip)
902 flipHandle = flipHandle != ( exceedsLeftEdge || exceedsRightEdge );
906 if( handle.actor && !handle.horizontallyFlipped )
908 // Change the anchor point to flip the image.
909 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_LEFT : AnchorPoint::TOP_RIGHT );
911 handle.horizontallyFlipped = true;
916 if( handle.actor && handle.horizontallyFlipped )
918 // Reset the anchor point.
919 handle.actor.SetAnchorPoint( isPrimaryHandle ? AnchorPoint::TOP_RIGHT : AnchorPoint::TOP_LEFT );
921 handle.horizontallyFlipped = false;
925 // Whether to flip the handle vertically.
926 handle.verticallyFlipped = ( verticallyFlippedPreferred &&
927 ( ( handleWorldPosition.y - handle.size.height ) > mBoundingBox.y ) ) ||
928 ( handleWorldPosition.y + handle.lineHeight + handle.size.height > mBoundingBox.w );
930 // The primary selection handle 'y' position in local coords.
931 // If the handle exceeds the bottom of the decoration box,
932 // set the 'y' position to the top of the line.
933 // The SetHandleImage() method will change the orientation.
934 const float yLocalPosition = handle.verticallyFlipped ? handle.position.y : handle.position.y + handle.lineHeight;
938 handle.actor.SetPosition( handle.position.x,
939 yLocalPosition ); // TODO : Fix for multiline.
943 void SetHandleImage( HandleType type )
945 HandleImpl& handle = mHandle[type];
947 HandleType markerType = HANDLE_TYPE_COUNT;
948 // If the selection handle is flipped it chooses the image of the other selection handle. Does nothing for the grab handle.
949 if( LEFT_SELECTION_HANDLE == type )
951 type = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE : LEFT_SELECTION_HANDLE;
952 markerType = handle.horizontallyFlipped ? RIGHT_SELECTION_HANDLE_MARKER : LEFT_SELECTION_HANDLE_MARKER;
954 else if( RIGHT_SELECTION_HANDLE == type )
956 type = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE : RIGHT_SELECTION_HANDLE;
957 markerType = handle.horizontallyFlipped ? LEFT_SELECTION_HANDLE_MARKER : RIGHT_SELECTION_HANDLE_MARKER;
960 // Chooses between the released or pressed image. It checks whether the pressed image exists.
963 const HandleImageType imageType = ( handle.pressed ? ( mHandleImages[type][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
965 handle.actor.SetImage( mHandleImages[type][imageType] );
968 if( HANDLE_TYPE_COUNT != markerType )
970 if( handle.markerActor )
972 const HandleImageType markerImageType = ( handle.pressed ? ( mHandleImages[markerType][HANDLE_IMAGE_PRESSED] ? HANDLE_IMAGE_PRESSED : HANDLE_IMAGE_RELEASED ) : HANDLE_IMAGE_RELEASED );
973 handle.markerActor.SetImage( mHandleImages[markerType][markerImageType] );
977 // Whether to flip the handle vertically.
980 handle.actor.SetOrientation( handle.verticallyFlipped ? ANGLE_180 : ANGLE_0, Vector3::XAXIS );
984 void CreateHighlight()
986 if( !mHighlightActor )
988 mHighlightActor = Actor::New();
990 #ifdef DECORATOR_DEBUG
991 mHighlightActor.SetName( "HighlightActor" );
993 mHighlightActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
994 mHighlightActor.SetSize( 1.0f, 1.0f );
995 mHighlightActor.SetColor( mHighlightColor );
996 mHighlightActor.SetColorMode( USE_OWN_COLOR );
999 // Add the highlight box telling the controller it needs clipping.
1000 mController.AddDecoration( mHighlightActor, true );
1003 void UpdateHighlight()
1005 if ( mHighlightActor )
1007 if( !mHighlightQuadList.empty() )
1009 Vector< Vector2 > vertices;
1010 Vector< unsigned int> indices;
1013 std::vector<QuadCoordinates>::iterator iter = mHighlightQuadList.begin();
1014 std::vector<QuadCoordinates>::iterator endIter = mHighlightQuadList.end();
1016 for( std::size_t v = 0; iter != endIter; ++iter,v+=4 )
1018 QuadCoordinates& quad = *iter;
1021 vertex.x = quad.min.x;
1022 vertex.y = quad.min.y;
1023 vertices.PushBack( vertex );
1026 vertex.x = quad.max.x;
1027 vertex.y = quad.min.y;
1028 vertices.PushBack( vertex );
1030 // bottom-left (v+2)
1031 vertex.x = quad.min.x;
1032 vertex.y = quad.max.y;
1033 vertices.PushBack( vertex );
1035 // bottom-right (v+3)
1036 vertex.x = quad.max.x;
1037 vertex.y = quad.max.y;
1038 vertices.PushBack( vertex );
1040 // triangle A (3, 1, 0)
1041 indices.PushBack( v + 3 );
1042 indices.PushBack( v + 1 );
1043 indices.PushBack( v );
1045 // triangle B (0, 2, 3)
1046 indices.PushBack( v );
1047 indices.PushBack( v + 2 );
1048 indices.PushBack( v + 3 );
1053 mQuadVertices.SetSize( vertices.Size() );
1057 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat, vertices.Size() );
1062 mQuadIndices.SetSize( indices.Size() );
1066 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat, indices.Size() );
1069 mQuadVertices.SetData( &vertices[ 0 ] );
1070 mQuadIndices.SetData( &indices[ 0 ] );
1072 if( !mQuadGeometry )
1074 mQuadGeometry = Geometry::New();
1075 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1077 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1079 if( !mHighlightRenderer )
1081 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1082 mHighlightActor.AddRenderer( mHighlightRenderer );
1086 mHighlightActor.SetPosition( mHighlightPosition.x,
1087 mHighlightPosition.y );
1089 mHighlightQuadList.clear();
1091 if( mHighlightRenderer )
1093 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1098 void OnTap( Actor actor, const TapGesture& tap )
1100 if( actor == mHandle[GRAB_HANDLE].actor )
1106 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1108 if( Gesture::Started == gesture.state )
1110 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1113 handle.grabDisplacementX += gesture.displacement.x;
1114 handle.grabDisplacementY += gesture.displacement.y;
1116 const float x = handle.position.x + handle.grabDisplacementX;
1117 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1119 if( Gesture::Started == gesture.state ||
1120 Gesture::Continuing == gesture.state )
1123 mController.GetTargetSize( targetSize );
1125 if( x < mScrollThreshold )
1127 mScrollDirection = SCROLL_RIGHT;
1128 mHandleScrolling = type;
1131 else if( x > targetSize.width - mScrollThreshold )
1133 mScrollDirection = SCROLL_LEFT;
1134 mHandleScrolling = type;
1139 mHandleScrolling = HANDLE_TYPE_COUNT;
1141 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1144 mHandlePanning = true;
1146 else if( Gesture::Finished == gesture.state ||
1147 Gesture::Cancelled == gesture.state )
1150 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1152 mNotifyEndOfScroll = false;
1153 mHandleScrolling = HANDLE_TYPE_COUNT;
1155 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1159 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1164 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1166 handle.pressed = false;
1168 mHandlePanning = false;
1172 void OnPan( Actor actor, const PanGesture& gesture )
1174 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1175 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1176 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1178 if( actor == grabHandle.grabArea )
1180 DoPan( grabHandle, GRAB_HANDLE, gesture );
1182 else if( actor == primarySelectionHandle.grabArea )
1184 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1186 else if( actor == secondarySelectionHandle.grabArea )
1188 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1192 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1194 // Switch between pressed/release grab-handle images
1195 if( event.GetPointCount() > 0 &&
1196 mHandle[GRAB_HANDLE].actor )
1198 const TouchPoint& point = event.GetPoint(0);
1200 if( TouchPoint::Down == point.state )
1202 mHandle[GRAB_HANDLE].pressed = true;
1204 else if( ( TouchPoint::Up == point.state ) ||
1205 ( TouchPoint::Interrupted == point.state ) )
1207 mHandle[GRAB_HANDLE].pressed = false;
1210 SetHandleImage( GRAB_HANDLE );
1213 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1217 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1219 // Switch between pressed/release selection handle images
1220 if( event.GetPointCount() > 0 &&
1221 mHandle[LEFT_SELECTION_HANDLE].actor )
1223 const TouchPoint& point = event.GetPoint(0);
1225 if( TouchPoint::Down == point.state )
1227 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1229 else if( ( TouchPoint::Up == point.state ) ||
1230 ( TouchPoint::Interrupted == point.state ) )
1232 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1233 mHandlePreviousCrossed = mHandleCurrentCrossed;
1234 mHandlePanning = false;
1237 SetHandleImage( LEFT_SELECTION_HANDLE );
1240 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1244 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1246 // Switch between pressed/release selection handle images
1247 if( event.GetPointCount() > 0 &&
1248 mHandle[RIGHT_SELECTION_HANDLE].actor )
1250 const TouchPoint& point = event.GetPoint(0);
1252 if( TouchPoint::Down == point.state )
1254 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1256 else if( ( TouchPoint::Up == point.state ) ||
1257 ( TouchPoint::Interrupted == point.state ) )
1259 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1260 mHandlePreviousCrossed = mHandleCurrentCrossed;
1261 mHandlePanning = false;
1264 SetHandleImage( RIGHT_SELECTION_HANDLE );
1267 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1271 void HandleResetPosition( PropertyNotification& source )
1273 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1275 if( grabHandle.active )
1277 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1278 SetGrabHandlePosition();
1280 // Sets the grab handle image according if it's pressed, flipped, etc.
1281 SetHandleImage( GRAB_HANDLE );
1285 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1286 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1288 // Sets the primary handle image according if it's pressed, flipped, etc.
1289 SetHandleImage( LEFT_SELECTION_HANDLE );
1291 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1292 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1294 // Sets the secondary handle image according if it's pressed, flipped, etc.
1295 SetHandleImage( RIGHT_SELECTION_HANDLE );
1299 void SetupActiveLayerPropertyNotifications()
1306 // Vertical notifications.
1308 // Disconnect any previous connected callback.
1309 if( mVerticalLessThanNotification )
1311 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1312 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1315 if( mVerticalGreaterThanNotification )
1317 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1318 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1321 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1322 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1323 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1325 if( grabHandle.active )
1327 if( grabHandle.verticallyFlipped )
1329 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1330 mVerticalGreaterThanNotification.Reset();
1332 // The vertical distance from the center of the active layer to the top edje of the display.
1333 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1335 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1336 LessThanCondition( mBoundingBox.y + topHeight ) );
1338 // Notifies the change from false to true and from true to false.
1339 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1341 // Connects the signals with the callbacks.
1342 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1346 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1347 mVerticalLessThanNotification.Reset();
1349 // The vertical distance from the center of the active layer to the bottom edje of the display.
1350 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1352 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1353 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1355 // Notifies the change from false to true and from true to false.
1356 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1358 // Connects the signals with the callbacks.
1359 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1362 else // The selection handles are active
1364 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1366 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1367 mVerticalGreaterThanNotification.Reset();
1369 // The vertical distance from the center of the active layer to the top edje of the display.
1370 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1372 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1373 LessThanCondition( mBoundingBox.y + topHeight ) );
1375 // Notifies the change from false to true and from true to false.
1376 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1378 // Connects the signals with the callbacks.
1379 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1381 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1383 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1384 mVerticalLessThanNotification.Reset();
1386 // The vertical distance from the center of the active layer to the bottom edje of the display.
1387 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1388 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1390 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1391 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1393 // Notifies the change from false to true and from true to false.
1394 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1396 // Connects the signals with the callbacks.
1397 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1401 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1403 // The vertical distance from the center of the active layer to the top edje of the display.
1404 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1405 -primaryHandle.position.y + primaryHandle.size.height :
1406 -secondaryHandle.position.y + secondaryHandle.size.height );
1408 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1409 LessThanCondition( mBoundingBox.y + topHeight ) );
1411 // Notifies the change from false to true and from true to false.
1412 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1414 // Connects the signals with the callbacks.
1415 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1417 // The vertical distance from the center of the active layer to the bottom edje of the display.
1418 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1419 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1420 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1422 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1423 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1425 // Notifies the change from false to true and from true to false.
1426 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1428 // Connects the signals with the callbacks.
1429 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1433 // Horizontal notifications.
1435 // Disconnect any previous connected callback.
1436 if( mHorizontalLessThanNotification )
1438 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1439 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1442 if( mHorizontalGreaterThanNotification )
1444 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1445 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1448 if( primaryHandle.active || secondaryHandle.active )
1450 // The horizontal distance from the center of the active layer to the left edje of the display.
1451 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1452 -secondaryHandle.position.x + secondaryHandle.size.width );
1454 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1455 LessThanCondition( mBoundingBox.x + leftWidth ) );
1457 // Notifies the change from false to true and from true to false.
1458 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1460 // Connects the signals with the callbacks.
1461 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1463 // The horizontal distance from the center of the active layer to the right edje of the display.
1464 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1465 secondaryHandle.position.x + secondaryHandle.size.width );
1467 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1468 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1470 // Notifies the change from false to true and from true to false.
1471 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1473 // Connects the signals with the callbacks.
1474 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1480 float AlternatePopUpPositionRelativeToCursor()
1482 float alternativePosition = 0.0f;
1484 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1486 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1487 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1488 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1489 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1491 if( primaryHandle.active || secondaryHandle.active )
1493 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1494 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1498 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1501 return alternativePosition;
1504 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1506 float alternativeYPosition = 0.0f;
1507 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1508 // if can't be positioned above, then position below row.
1509 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1511 mCopyPastePopup.actor.SetY( alternativeYPosition );
1514 void SetUpPopupPositionNotifications()
1516 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1518 // Exceeding vertical boundary
1520 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1522 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1523 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1524 mBoundingBox.w - popupHeight * 0.5f ) );
1526 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1529 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1531 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1533 // Parent must already by added to Stage for these Get calls to work
1534 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1535 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1537 // Calculate distance to move popup (in local space) so fits within the boundary
1538 float xOffSetToKeepWithinBounds = 0.0f;
1539 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1541 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1543 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1545 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1548 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1549 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1551 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1554 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1556 // Prevent pixel mis-alignment by rounding down.
1557 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1558 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1561 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1563 HandleImpl& handle = mHandle[handleType];
1564 handle.size = Size( image.GetWidth(), image.GetHeight() );
1566 mHandleImages[handleType][handleImageType] = image;
1569 void SetScrollThreshold( float threshold )
1571 mScrollThreshold = threshold;
1574 float GetScrollThreshold() const
1576 return mScrollThreshold;
1579 void SetScrollSpeed( float speed )
1581 mScrollSpeed = speed;
1582 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1585 float GetScrollSpeed() const
1587 return mScrollSpeed;
1590 void NotifyEndOfScroll()
1596 mNotifyEndOfScroll = true;
1601 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1603 * It only starts the timer if it's already created.
1605 void StartScrollTimer()
1609 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1610 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1613 if( !mScrollTimer.IsRunning() )
1615 mScrollTimer.Start();
1620 * Stops the timer used to scroll the text.
1622 void StopScrollTimer()
1626 mScrollTimer.Stop();
1631 * Callback called by the timer used to scroll the text.
1633 * It calculates and sets a new scroll position.
1635 bool OnScrollTimerTick()
1637 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1639 mController.DecorationEvent( mHandleScrolling,
1641 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1648 ControllerInterface& mController;
1650 TapGestureDetector mTapDetector;
1651 PanGestureDetector mPanGestureDetector;
1652 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1653 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1655 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1656 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1657 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1658 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1659 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1660 Control mPrimaryCursor;
1661 Control mSecondaryCursor;
1663 Actor mHighlightActor; ///< Actor to display highlight
1664 Renderer mHighlightRenderer;
1665 Material mHighlightMaterial; ///< Material used for highlight
1666 Property::Map mQuadVertexFormat;
1667 Property::Map mQuadIndexFormat;
1668 PopupImpl mCopyPastePopup;
1669 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1670 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1672 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1673 Vector4 mHandleColor;
1675 CursorImpl mCursor[CURSOR_COUNT];
1676 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1678 PropertyBuffer mQuadVertices;
1679 PropertyBuffer mQuadIndices;
1680 Geometry mQuadGeometry;
1681 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1683 Vector4 mBoundingBox; ///< The bounding box in world coords.
1684 Vector4 mHighlightColor; ///< Color of the highlight
1685 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1686 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1688 unsigned int mActiveCursor;
1689 unsigned int mCursorBlinkInterval;
1690 float mCursorBlinkDuration;
1691 float mCursorWidth; ///< The width of the cursors in pixels.
1692 HandleType mHandleScrolling; ///< The handle which is scrolling.
1693 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1694 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1695 float mScrollSpeed; ///< The scroll speed in pixels per second.
1696 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1697 int mTextDepth; ///< The depth used to render the text.
1699 bool mActiveCopyPastePopup : 1;
1700 bool mPopupSetNewPosition : 1;
1701 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1702 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1703 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1704 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1705 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1706 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1707 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1708 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1709 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1710 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1711 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1714 DecoratorPtr Decorator::New( ControllerInterface& controller,
1715 TextSelectionPopupCallbackInterface& callbackInterface )
1717 return DecoratorPtr( new Decorator( controller,
1718 callbackInterface ) );
1721 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1723 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1726 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1728 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1731 void Decorator::Relayout( const Vector2& size )
1733 mImpl->Relayout( size );
1736 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1738 mImpl->UpdatePositions( scrollOffset );
1743 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1745 mImpl->mActiveCursor = activeCursor;
1748 unsigned int Decorator::GetActiveCursor() const
1750 return mImpl->mActiveCursor;
1753 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1755 mImpl->mCursor[cursor].position.x = x;
1756 mImpl->mCursor[cursor].position.y = y;
1757 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1758 mImpl->mCursor[cursor].lineHeight = lineHeight;
1761 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1763 x = mImpl->mCursor[cursor].position.x;
1764 y = mImpl->mCursor[cursor].position.y;
1765 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1766 lineHeight = mImpl->mCursor[cursor].lineHeight;
1769 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1771 return mImpl->mCursor[cursor].position;
1774 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1776 mImpl->mCursor[cursor].color = color;
1779 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1781 return mImpl->mCursor[cursor].color;
1784 void Decorator::StartCursorBlink()
1786 if ( !mImpl->mCursorBlinkTimer )
1788 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1789 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1792 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1794 mImpl->mCursorBlinkTimer.Start();
1798 void Decorator::StopCursorBlink()
1800 if ( mImpl->mCursorBlinkTimer )
1802 mImpl->mCursorBlinkTimer.Stop();
1805 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1808 void Decorator::DelayCursorBlink()
1810 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1811 mImpl->mDelayCursorBlink = true;
1814 void Decorator::SetCursorBlinkInterval( float seconds )
1816 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1819 float Decorator::GetCursorBlinkInterval() const
1821 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1824 void Decorator::SetCursorBlinkDuration( float seconds )
1826 mImpl->mCursorBlinkDuration = seconds;
1829 float Decorator::GetCursorBlinkDuration() const
1831 return mImpl->mCursorBlinkDuration;
1834 void Decorator::SetCursorWidth( int width )
1836 mImpl->mCursorWidth = static_cast<float>( width );
1839 int Decorator::GetCursorWidth() const
1841 return static_cast<int>( mImpl->mCursorWidth );
1846 void Decorator::SetHandleActive( HandleType handleType, bool active )
1848 mImpl->mHandle[handleType].active = active;
1852 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1854 mImpl->mHandlePreviousCrossed = false;
1857 // TODO: this is a work-around.
1858 // The problem is the handle actor does not receive the touch event with the Interrupt
1859 // state when the power button is pressed and the application goes to background.
1860 mImpl->mHandle[handleType].pressed = false;
1861 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1862 ImageView imageView = mImpl->mHandle[handleType].actor;
1863 if( imageReleased && imageView )
1865 imageView.SetImage( imageReleased );
1871 bool Decorator::IsHandleActive( HandleType handleType ) const
1873 return mImpl->mHandle[handleType].active ;
1876 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1878 mImpl->SetHandleImage( handleType, handleImageType, image );
1881 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1883 return mImpl->mHandleImages[handleType][handleImageType];
1886 void Decorator::SetHandleColor( const Vector4& color )
1888 mImpl->mHandleColor = color;
1891 const Vector4& Decorator::GetHandleColor() const
1893 return mImpl->mHandleColor;
1896 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1898 // Adjust handle's displacement
1899 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1901 handle.grabDisplacementX -= x - handle.position.x;
1902 handle.grabDisplacementY -= y - handle.position.y;
1904 handle.position.x = x;
1905 handle.position.y = y;
1906 handle.lineHeight = height;
1909 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1911 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1913 x = handle.position.x;
1914 y = handle.position.y;
1915 height = handle.lineHeight;
1918 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1920 return mImpl->mHandle[handleType].position;
1923 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1925 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1928 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1930 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1933 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1935 mImpl->mFlipSelectionHandlesOnCross = enable;
1938 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1940 mImpl->mHandleCurrentCrossed = indicesSwapped;
1941 mImpl->mFlipLeftSelectionHandleDirection = left;
1942 mImpl->mFlipRightSelectionHandleDirection = right;
1945 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1947 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1950 void Decorator::ClearHighlights()
1952 mImpl->mHighlightQuadList.clear();
1953 mImpl->mHighlightPosition = Vector2::ZERO;
1956 void Decorator::SetHighlightColor( const Vector4& color )
1958 mImpl->mHighlightColor = color;
1961 const Vector4& Decorator::GetHighlightColor() const
1963 return mImpl->mHighlightColor;
1966 void Decorator::SetTextDepth( int textDepth )
1968 mImpl->mTextDepth = textDepth;
1971 void Decorator::SetPopupActive( bool active )
1973 mImpl->mActiveCopyPastePopup = active;
1976 bool Decorator::IsPopupActive() const
1978 return mImpl->mActiveCopyPastePopup ;
1981 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1983 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1985 if ( !mImpl->mCopyPastePopup.actor )
1987 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1988 #ifdef DECORATOR_DEBUG
1989 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1991 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1992 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1995 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1998 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
2000 return mImpl->mEnabledPopupButtons;
2005 void Decorator::SetScrollThreshold( float threshold )
2007 mImpl->SetScrollThreshold( threshold );
2010 float Decorator::GetScrollThreshold() const
2012 return mImpl->GetScrollThreshold();
2015 void Decorator::SetScrollSpeed( float speed )
2017 mImpl->SetScrollSpeed( speed );
2020 float Decorator::GetScrollSpeed() const
2022 return mImpl->GetScrollSpeed();
2025 void Decorator::NotifyEndOfScroll()
2027 mImpl->NotifyEndOfScroll();
2030 Decorator::~Decorator()
2035 Decorator::Decorator( ControllerInterface& controller,
2036 TextSelectionPopupCallbackInterface& callbackInterface )
2039 mImpl = new Decorator::Impl( controller, callbackInterface );
2044 } // namespace Toolkit