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 );
1051 if( ! mQuadVertices )
1053 mQuadVertices = PropertyBuffer::New( mQuadVertexFormat );
1056 if( ! mQuadIndices )
1058 mQuadIndices = PropertyBuffer::New( mQuadIndexFormat );
1061 mQuadVertices.SetData( &vertices[ 0 ], vertices.Size() );
1062 mQuadIndices.SetData( &indices[ 0 ], indices.Size() );
1064 if( !mQuadGeometry )
1066 mQuadGeometry = Geometry::New();
1067 mQuadGeometry.AddVertexBuffer( mQuadVertices );
1069 mQuadGeometry.SetIndexBuffer( mQuadIndices );
1071 if( !mHighlightRenderer )
1073 mHighlightRenderer = Dali::Renderer::New( mQuadGeometry, mHighlightMaterial );
1074 mHighlightActor.AddRenderer( mHighlightRenderer );
1078 mHighlightActor.SetPosition( mHighlightPosition.x,
1079 mHighlightPosition.y );
1081 mHighlightQuadList.clear();
1083 if( mHighlightRenderer )
1085 mHighlightRenderer.SetProperty( Renderer::Property::DEPTH_INDEX, mTextDepth - 2 ); // text is rendered at mTextDepth and text's shadow at mTextDepth -1u.
1090 void OnTap( Actor actor, const TapGesture& tap )
1092 if( actor == mHandle[GRAB_HANDLE].actor )
1098 void DoPan( HandleImpl& handle, HandleType type, const PanGesture& gesture )
1100 if( Gesture::Started == gesture.state )
1102 handle.grabDisplacementX = handle.grabDisplacementY = 0;
1105 handle.grabDisplacementX += gesture.displacement.x;
1106 handle.grabDisplacementY += gesture.displacement.y;
1108 const float x = handle.position.x + handle.grabDisplacementX;
1109 const float y = handle.position.y + handle.lineHeight*0.5f + handle.grabDisplacementY;
1111 if( Gesture::Started == gesture.state ||
1112 Gesture::Continuing == gesture.state )
1115 mController.GetTargetSize( targetSize );
1117 if( x < mScrollThreshold )
1119 mScrollDirection = SCROLL_RIGHT;
1120 mHandleScrolling = type;
1123 else if( x > targetSize.width - mScrollThreshold )
1125 mScrollDirection = SCROLL_LEFT;
1126 mHandleScrolling = type;
1131 mHandleScrolling = HANDLE_TYPE_COUNT;
1133 mController.DecorationEvent( type, HANDLE_PRESSED, x, y );
1136 mHandlePanning = true;
1138 else if( Gesture::Finished == gesture.state ||
1139 Gesture::Cancelled == gesture.state )
1142 ( mScrollTimer.IsRunning() || mNotifyEndOfScroll ) )
1144 mNotifyEndOfScroll = false;
1145 mHandleScrolling = HANDLE_TYPE_COUNT;
1147 mController.DecorationEvent( type, HANDLE_STOP_SCROLLING, x, y );
1151 mController.DecorationEvent( type, HANDLE_RELEASED, x, y );
1156 handle.actor.SetImage( mHandleImages[type][HANDLE_IMAGE_RELEASED] );
1158 handle.pressed = false;
1160 mHandlePanning = false;
1164 void OnPan( Actor actor, const PanGesture& gesture )
1166 HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1167 HandleImpl& primarySelectionHandle = mHandle[LEFT_SELECTION_HANDLE];
1168 HandleImpl& secondarySelectionHandle = mHandle[RIGHT_SELECTION_HANDLE];
1170 if( actor == grabHandle.grabArea )
1172 DoPan( grabHandle, GRAB_HANDLE, gesture );
1174 else if( actor == primarySelectionHandle.grabArea )
1176 DoPan( primarySelectionHandle, LEFT_SELECTION_HANDLE, gesture );
1178 else if( actor == secondarySelectionHandle.grabArea )
1180 DoPan( secondarySelectionHandle, RIGHT_SELECTION_HANDLE, gesture );
1184 bool OnGrabHandleTouched( Actor actor, const TouchEvent& event )
1186 // Switch between pressed/release grab-handle images
1187 if( event.GetPointCount() > 0 &&
1188 mHandle[GRAB_HANDLE].actor )
1190 const TouchPoint& point = event.GetPoint(0);
1192 if( TouchPoint::Down == point.state )
1194 mHandle[GRAB_HANDLE].pressed = true;
1196 else if( ( TouchPoint::Up == point.state ) ||
1197 ( TouchPoint::Interrupted == point.state ) )
1199 mHandle[GRAB_HANDLE].pressed = false;
1202 SetHandleImage( GRAB_HANDLE );
1205 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1209 bool OnHandleOneTouched( Actor actor, const TouchEvent& event )
1211 // Switch between pressed/release selection handle images
1212 if( event.GetPointCount() > 0 &&
1213 mHandle[LEFT_SELECTION_HANDLE].actor )
1215 const TouchPoint& point = event.GetPoint(0);
1217 if( TouchPoint::Down == point.state )
1219 mHandle[LEFT_SELECTION_HANDLE].pressed = true;
1221 else if( ( TouchPoint::Up == point.state ) ||
1222 ( TouchPoint::Interrupted == point.state ) )
1224 mHandle[LEFT_SELECTION_HANDLE].pressed = false;
1225 mHandlePreviousCrossed = mHandleCurrentCrossed;
1226 mHandlePanning = false;
1229 SetHandleImage( LEFT_SELECTION_HANDLE );
1232 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1236 bool OnHandleTwoTouched( Actor actor, const TouchEvent& event )
1238 // Switch between pressed/release selection handle images
1239 if( event.GetPointCount() > 0 &&
1240 mHandle[RIGHT_SELECTION_HANDLE].actor )
1242 const TouchPoint& point = event.GetPoint(0);
1244 if( TouchPoint::Down == point.state )
1246 mHandle[RIGHT_SELECTION_HANDLE].pressed = true;
1248 else if( ( TouchPoint::Up == point.state ) ||
1249 ( TouchPoint::Interrupted == point.state ) )
1251 mHandle[RIGHT_SELECTION_HANDLE].pressed = false;
1252 mHandlePreviousCrossed = mHandleCurrentCrossed;
1253 mHandlePanning = false;
1256 SetHandleImage( RIGHT_SELECTION_HANDLE );
1259 // Consume to avoid pop-ups accidentally closing, when handle is outside of pop-up area
1263 void HandleResetPosition( PropertyNotification& source )
1265 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1267 if( grabHandle.active )
1269 // Sets the grab handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1270 SetGrabHandlePosition();
1272 // Sets the grab handle image according if it's pressed, flipped, etc.
1273 SetHandleImage( GRAB_HANDLE );
1277 // Sets the primary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1278 SetSelectionHandlePosition( LEFT_SELECTION_HANDLE );
1280 // Sets the primary handle image according if it's pressed, flipped, etc.
1281 SetHandleImage( LEFT_SELECTION_HANDLE );
1283 // Sets the secondary selection handle position and calculates if it needs to be vertically flipped if it exceeds the boundary box.
1284 SetSelectionHandlePosition( RIGHT_SELECTION_HANDLE );
1286 // Sets the secondary handle image according if it's pressed, flipped, etc.
1287 SetHandleImage( RIGHT_SELECTION_HANDLE );
1291 void SetupActiveLayerPropertyNotifications()
1298 // Vertical notifications.
1300 // Disconnect any previous connected callback.
1301 if( mVerticalLessThanNotification )
1303 mVerticalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1304 mActiveLayer.RemovePropertyNotification( mVerticalLessThanNotification );
1307 if( mVerticalGreaterThanNotification )
1309 mVerticalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1310 mActiveLayer.RemovePropertyNotification( mVerticalGreaterThanNotification );
1313 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1314 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1315 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1317 if( grabHandle.active )
1319 if( grabHandle.verticallyFlipped )
1321 // The grab handle is vertically flipped. Never is going to exceed the bottom edje of the display.
1322 mVerticalGreaterThanNotification.Reset();
1324 // The vertical distance from the center of the active layer to the top edje of the display.
1325 const float topHeight = 0.5f * mControlSize.height - grabHandle.position.y + grabHandle.size.height;
1327 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1328 LessThanCondition( mBoundingBox.y + topHeight ) );
1330 // Notifies the change from false to true and from true to false.
1331 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1333 // Connects the signals with the callbacks.
1334 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1338 // The grab handle is not vertically flipped. Never is going to exceed the top edje of the display.
1339 mVerticalLessThanNotification.Reset();
1341 // The vertical distance from the center of the active layer to the bottom edje of the display.
1342 const float bottomHeight = -0.5f * mControlSize.height + grabHandle.position.y + grabHandle.lineHeight + grabHandle.size.height;
1344 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1345 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1347 // Notifies the change from false to true and from true to false.
1348 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1350 // Connects the signals with the callbacks.
1351 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1354 else // The selection handles are active
1356 if( primaryHandle.verticallyFlipped && secondaryHandle.verticallyFlipped )
1358 // Both selection handles are vertically flipped. Never are going to exceed the bottom edje of the display.
1359 mVerticalGreaterThanNotification.Reset();
1361 // The vertical distance from the center of the active layer to the top edje of the display.
1362 const float topHeight = 0.5f * mControlSize.height + std::max( -primaryHandle.position.y + primaryHandle.size.height, -secondaryHandle.position.y + secondaryHandle.size.height );
1364 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1365 LessThanCondition( mBoundingBox.y + topHeight ) );
1367 // Notifies the change from false to true and from true to false.
1368 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1370 // Connects the signals with the callbacks.
1371 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1373 else if( !primaryHandle.verticallyFlipped && !secondaryHandle.verticallyFlipped )
1375 // Both selection handles aren't vertically flipped. Never are going to exceed the top edje of the display.
1376 mVerticalLessThanNotification.Reset();
1378 // The vertical distance from the center of the active layer to the bottom edje of the display.
1379 const float bottomHeight = -0.5f * mControlSize.height + std::max( primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height,
1380 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height );
1382 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1383 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1385 // Notifies the change from false to true and from true to false.
1386 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1388 // Connects the signals with the callbacks.
1389 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1393 // Only one of the selection handles is vertically flipped. Both vertical notifications are needed.
1395 // The vertical distance from the center of the active layer to the top edje of the display.
1396 const float topHeight = 0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1397 -primaryHandle.position.y + primaryHandle.size.height :
1398 -secondaryHandle.position.y + secondaryHandle.size.height );
1400 mVerticalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1401 LessThanCondition( mBoundingBox.y + topHeight ) );
1403 // Notifies the change from false to true and from true to false.
1404 mVerticalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1406 // Connects the signals with the callbacks.
1407 mVerticalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1409 // The vertical distance from the center of the active layer to the bottom edje of the display.
1410 const float bottomHeight = -0.5f * mControlSize.height + ( primaryHandle.verticallyFlipped ?
1411 secondaryHandle.position.y + secondaryHandle.lineHeight + secondaryHandle.size.height :
1412 primaryHandle.position.y + primaryHandle.lineHeight + primaryHandle.size.height );
1414 mVerticalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1415 GreaterThanCondition( mBoundingBox.w - bottomHeight ) );
1417 // Notifies the change from false to true and from true to false.
1418 mVerticalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1420 // Connects the signals with the callbacks.
1421 mVerticalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1425 // Horizontal notifications.
1427 // Disconnect any previous connected callback.
1428 if( mHorizontalLessThanNotification )
1430 mHorizontalLessThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1431 mActiveLayer.RemovePropertyNotification( mHorizontalLessThanNotification );
1434 if( mHorizontalGreaterThanNotification )
1436 mHorizontalGreaterThanNotification.NotifySignal().Disconnect( this, &Decorator::Impl::HandleResetPosition );
1437 mActiveLayer.RemovePropertyNotification( mHorizontalGreaterThanNotification );
1440 if( primaryHandle.active || secondaryHandle.active )
1442 // The horizontal distance from the center of the active layer to the left edje of the display.
1443 const float leftWidth = 0.5f * mControlSize.width + std::max( -primaryHandle.position.x + primaryHandle.size.width,
1444 -secondaryHandle.position.x + secondaryHandle.size.width );
1446 mHorizontalLessThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1447 LessThanCondition( mBoundingBox.x + leftWidth ) );
1449 // Notifies the change from false to true and from true to false.
1450 mHorizontalLessThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1452 // Connects the signals with the callbacks.
1453 mHorizontalLessThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1455 // The horizontal distance from the center of the active layer to the right edje of the display.
1456 const float rightWidth = -0.5f * mControlSize.width + std::max( primaryHandle.position.x + primaryHandle.size.width,
1457 secondaryHandle.position.x + secondaryHandle.size.width );
1459 mHorizontalGreaterThanNotification = mActiveLayer.AddPropertyNotification( Actor::Property::WORLD_POSITION_X,
1460 GreaterThanCondition( mBoundingBox.z - rightWidth ) );
1462 // Notifies the change from false to true and from true to false.
1463 mHorizontalGreaterThanNotification.SetNotifyMode( PropertyNotification::NotifyOnChanged );
1465 // Connects the signals with the callbacks.
1466 mHorizontalGreaterThanNotification.NotifySignal().Connect( this, &Decorator::Impl::HandleResetPosition );
1472 float AlternatePopUpPositionRelativeToCursor()
1474 float alternativePosition = 0.0f;
1476 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1478 const HandleImpl& primaryHandle = mHandle[LEFT_SELECTION_HANDLE];
1479 const HandleImpl& secondaryHandle = mHandle[RIGHT_SELECTION_HANDLE];
1480 const HandleImpl& grabHandle = mHandle[GRAB_HANDLE];
1481 const CursorImpl& cursor = mCursor[PRIMARY_CURSOR];
1483 if( primaryHandle.active || secondaryHandle.active )
1485 const float maxHandleHeight = std::max( primaryHandle.size.height, secondaryHandle.size.height );
1486 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + maxHandleHeight + std::min( primaryHandle.position.y, secondaryHandle.position.y );
1490 alternativePosition = 0.5f * popupHeight + cursor.lineHeight + grabHandle.size.height + cursor.position.y;
1493 return alternativePosition;
1496 void PopUpLeavesVerticalBoundary( PropertyNotification& source )
1498 float alternativeYPosition = 0.0f;
1499 // todo use AlternatePopUpPositionRelativeToSelectionHandles() if text is highlighted
1500 // if can't be positioned above, then position below row.
1501 alternativeYPosition = AlternatePopUpPositionRelativeToCursor();
1503 mCopyPastePopup.actor.SetY( alternativeYPosition );
1506 void SetUpPopupPositionNotifications()
1508 // Note Property notifications ignore any set anchor point so conditions must allow for this. Default is Top Left.
1510 // Exceeding vertical boundary
1512 const float popupHeight = mCopyPastePopup.actor.GetRelayoutSize( Dimension::HEIGHT );
1514 PropertyNotification verticalExceedNotification = mCopyPastePopup.actor.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
1515 OutsideCondition( mBoundingBox.y + popupHeight * 0.5f,
1516 mBoundingBox.w - popupHeight * 0.5f ) );
1518 verticalExceedNotification.NotifySignal().Connect( this, &Decorator::Impl::PopUpLeavesVerticalBoundary );
1521 void GetConstrainedPopupPosition( Vector3& requiredPopupPosition, const Vector3& popupDistanceFromAnchorPoint, Actor parent, const Vector4& boundingRectangleWorld )
1523 DALI_ASSERT_DEBUG ( "Popup parent not on stage" && parent.OnStage() )
1525 // Parent must already by added to Stage for these Get calls to work
1526 const Vector3 parentWorldPositionLeftAnchor = parent.GetCurrentWorldPosition() - parent.GetCurrentSize() * parent.GetCurrentAnchorPoint();
1527 const Vector3 popupWorldPosition = parentWorldPositionLeftAnchor + requiredPopupPosition; // Parent World position plus popup local position gives World Position
1529 // Calculate distance to move popup (in local space) so fits within the boundary
1530 float xOffSetToKeepWithinBounds = 0.0f;
1531 if( popupWorldPosition.x - popupDistanceFromAnchorPoint.x < boundingRectangleWorld.x )
1533 xOffSetToKeepWithinBounds = boundingRectangleWorld.x - ( popupWorldPosition.x - popupDistanceFromAnchorPoint.x );
1535 else if( popupWorldPosition.x + popupDistanceFromAnchorPoint.x > boundingRectangleWorld.z )
1537 xOffSetToKeepWithinBounds = boundingRectangleWorld.z - ( popupWorldPosition.x + popupDistanceFromAnchorPoint.x );
1540 // Ensure initial display of Popup is in alternative position if can not fit above. As Property notification will be a frame behind.
1541 if( popupWorldPosition.y - popupDistanceFromAnchorPoint.y < boundingRectangleWorld.y )
1543 requiredPopupPosition.y = AlternatePopUpPositionRelativeToCursor();
1546 requiredPopupPosition.x = requiredPopupPosition.x + xOffSetToKeepWithinBounds;
1548 // Prevent pixel mis-alignment by rounding down.
1549 requiredPopupPosition.x = floor( requiredPopupPosition.x );
1550 requiredPopupPosition.y = floor( requiredPopupPosition.y );
1553 void SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1555 HandleImpl& handle = mHandle[handleType];
1556 handle.size = Size( image.GetWidth(), image.GetHeight() );
1558 mHandleImages[handleType][handleImageType] = image;
1561 void SetScrollThreshold( float threshold )
1563 mScrollThreshold = threshold;
1566 float GetScrollThreshold() const
1568 return mScrollThreshold;
1571 void SetScrollSpeed( float speed )
1573 mScrollSpeed = speed;
1574 mScrollDistance = speed * SCROLL_TICK_INTERVAL * TO_SECONDS;
1577 float GetScrollSpeed() const
1579 return mScrollSpeed;
1582 void NotifyEndOfScroll()
1588 mNotifyEndOfScroll = true;
1593 * Creates and starts a timer to scroll the text when handles are close to the edges of the text.
1595 * It only starts the timer if it's already created.
1597 void StartScrollTimer()
1601 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1602 mScrollTimer.TickSignal().Connect( this, &Decorator::Impl::OnScrollTimerTick );
1605 if( !mScrollTimer.IsRunning() )
1607 mScrollTimer.Start();
1612 * Stops the timer used to scroll the text.
1614 void StopScrollTimer()
1618 mScrollTimer.Stop();
1623 * Callback called by the timer used to scroll the text.
1625 * It calculates and sets a new scroll position.
1627 bool OnScrollTimerTick()
1629 if( HANDLE_TYPE_COUNT != mHandleScrolling )
1631 mController.DecorationEvent( mHandleScrolling,
1633 mScrollDirection == SCROLL_RIGHT ? mScrollDistance : -mScrollDistance,
1640 ControllerInterface& mController;
1642 TapGestureDetector mTapDetector;
1643 PanGestureDetector mPanGestureDetector;
1644 Timer mCursorBlinkTimer; ///< Timer to signal cursor to blink
1645 Timer mScrollTimer; ///< Timer used to scroll the text when the grab handle is moved close to the edges.
1647 Layer mActiveLayer; ///< Layer for active handles and alike that ensures they are above all else.
1648 PropertyNotification mVerticalLessThanNotification; ///< Notifies when the 'y' coord of the active layer is less than a given value.
1649 PropertyNotification mVerticalGreaterThanNotification; ///< Notifies when the 'y' coord of the active layer is grater than a given value.
1650 PropertyNotification mHorizontalLessThanNotification; ///< Notifies when the 'x' coord of the active layer is less than a given value.
1651 PropertyNotification mHorizontalGreaterThanNotification; ///< Notifies when the 'x' coord of the active layer is grater than a given value.
1652 Control mPrimaryCursor;
1653 Control mSecondaryCursor;
1655 Actor mHighlightActor; ///< Actor to display highlight
1656 Renderer mHighlightRenderer;
1657 Material mHighlightMaterial; ///< Material used for highlight
1658 Property::Map mQuadVertexFormat;
1659 Property::Map mQuadIndexFormat;
1660 PopupImpl mCopyPastePopup;
1661 TextSelectionPopup::Buttons mEnabledPopupButtons; /// Bit mask of currently enabled Popup buttons
1662 TextSelectionPopupCallbackInterface& mTextSelectionPopupCallbackInterface;
1664 Image mHandleImages[HANDLE_TYPE_COUNT][HANDLE_IMAGE_TYPE_COUNT];
1665 Vector4 mHandleColor;
1667 CursorImpl mCursor[CURSOR_COUNT];
1668 HandleImpl mHandle[HANDLE_TYPE_COUNT];
1670 PropertyBuffer mQuadVertices;
1671 PropertyBuffer mQuadIndices;
1672 Geometry mQuadGeometry;
1673 QuadContainer mHighlightQuadList; ///< Sub-selections that combine to create the complete selection highlight
1675 Vector4 mBoundingBox; ///< The bounding box in world coords.
1676 Vector4 mHighlightColor; ///< Color of the highlight
1677 Vector2 mHighlightPosition; ///< The position of the highlight actor.
1678 Vector2 mControlSize; ///< The control's size. Set by the Relayout.
1680 unsigned int mActiveCursor;
1681 unsigned int mCursorBlinkInterval;
1682 float mCursorBlinkDuration;
1683 float mCursorWidth; ///< The width of the cursors in pixels.
1684 HandleType mHandleScrolling; ///< The handle which is scrolling.
1685 ScrollDirection mScrollDirection; ///< The direction of the scroll.
1686 float mScrollThreshold; ///< Defines a square area inside the control, close to the edge. A cursor entering this area will trigger scroll events.
1687 float mScrollSpeed; ///< The scroll speed in pixels per second.
1688 float mScrollDistance; ///< Distance the text scrolls during a scroll interval.
1689 int mTextDepth; ///< The depth used to render the text.
1691 bool mActiveCopyPastePopup : 1;
1692 bool mPopupSetNewPosition : 1;
1693 bool mCursorBlinkStatus : 1; ///< Flag to switch between blink on and blink off.
1694 bool mDelayCursorBlink : 1; ///< Used to avoid cursor blinking when entering text.
1695 bool mPrimaryCursorVisible : 1; ///< Whether the primary cursor is visible.
1696 bool mSecondaryCursorVisible : 1; ///< Whether the secondary cursor is visible.
1697 bool mFlipSelectionHandlesOnCross : 1; ///< Whether to flip the selection handles as soon as they cross.
1698 bool mFlipLeftSelectionHandleDirection : 1; ///< Whether to flip the left selection handle image because of the character's direction.
1699 bool mFlipRightSelectionHandleDirection : 1; ///< Whether to flip the right selection handle image because of the character's direction.
1700 bool mHandlePanning : 1; ///< Whether any of the handles is moving.
1701 bool mHandleCurrentCrossed : 1; ///< Whether the handles are crossed.
1702 bool mHandlePreviousCrossed : 1; ///< Whether the handles where crossed at the last handle touch up.
1703 bool mNotifyEndOfScroll : 1; ///< Whether to notify the end of the scroll.
1706 DecoratorPtr Decorator::New( ControllerInterface& controller,
1707 TextSelectionPopupCallbackInterface& callbackInterface )
1709 return DecoratorPtr( new Decorator( controller,
1710 callbackInterface ) );
1713 void Decorator::SetBoundingBox( const Rect<int>& boundingBox )
1715 LocalToWorldCoordinatesBoundingBox( boundingBox, mImpl->mBoundingBox );
1718 void Decorator::GetBoundingBox( Rect<int>& boundingBox ) const
1720 WorldToLocalCoordinatesBoundingBox( mImpl->mBoundingBox, boundingBox );
1723 void Decorator::Relayout( const Vector2& size )
1725 mImpl->Relayout( size );
1728 void Decorator::UpdatePositions( const Vector2& scrollOffset )
1730 mImpl->UpdatePositions( scrollOffset );
1735 void Decorator::SetActiveCursor( ActiveCursor activeCursor )
1737 mImpl->mActiveCursor = activeCursor;
1740 unsigned int Decorator::GetActiveCursor() const
1742 return mImpl->mActiveCursor;
1745 void Decorator::SetPosition( Cursor cursor, float x, float y, float cursorHeight, float lineHeight )
1747 mImpl->mCursor[cursor].position.x = x;
1748 mImpl->mCursor[cursor].position.y = y;
1749 mImpl->mCursor[cursor].cursorHeight = cursorHeight;
1750 mImpl->mCursor[cursor].lineHeight = lineHeight;
1753 void Decorator::GetPosition( Cursor cursor, float& x, float& y, float& cursorHeight, float& lineHeight ) const
1755 x = mImpl->mCursor[cursor].position.x;
1756 y = mImpl->mCursor[cursor].position.y;
1757 cursorHeight = mImpl->mCursor[cursor].cursorHeight;
1758 lineHeight = mImpl->mCursor[cursor].lineHeight;
1761 const Vector2& Decorator::GetPosition( Cursor cursor ) const
1763 return mImpl->mCursor[cursor].position;
1766 void Decorator::SetCursorColor( Cursor cursor, const Dali::Vector4& color )
1768 mImpl->mCursor[cursor].color = color;
1771 const Dali::Vector4& Decorator::GetColor( Cursor cursor ) const
1773 return mImpl->mCursor[cursor].color;
1776 void Decorator::StartCursorBlink()
1778 if ( !mImpl->mCursorBlinkTimer )
1780 mImpl->mCursorBlinkTimer = Timer::New( mImpl->mCursorBlinkInterval );
1781 mImpl->mCursorBlinkTimer.TickSignal().Connect( mImpl, &Decorator::Impl::OnCursorBlinkTimerTick );
1784 if ( !mImpl->mCursorBlinkTimer.IsRunning() )
1786 mImpl->mCursorBlinkTimer.Start();
1790 void Decorator::StopCursorBlink()
1792 if ( mImpl->mCursorBlinkTimer )
1794 mImpl->mCursorBlinkTimer.Stop();
1797 mImpl->mCursorBlinkStatus = true; // Keep cursor permanently shown
1800 void Decorator::DelayCursorBlink()
1802 mImpl->mCursorBlinkStatus = true; // Show cursor for a bit longer
1803 mImpl->mDelayCursorBlink = true;
1806 void Decorator::SetCursorBlinkInterval( float seconds )
1808 mImpl->mCursorBlinkInterval = static_cast<unsigned int>( seconds * TO_MILLISECONDS ); // Convert to milliseconds
1811 float Decorator::GetCursorBlinkInterval() const
1813 return static_cast<float>( mImpl->mCursorBlinkInterval ) * TO_SECONDS;
1816 void Decorator::SetCursorBlinkDuration( float seconds )
1818 mImpl->mCursorBlinkDuration = seconds;
1821 float Decorator::GetCursorBlinkDuration() const
1823 return mImpl->mCursorBlinkDuration;
1826 void Decorator::SetCursorWidth( int width )
1828 mImpl->mCursorWidth = static_cast<float>( width );
1831 int Decorator::GetCursorWidth() const
1833 return static_cast<int>( mImpl->mCursorWidth );
1838 void Decorator::SetHandleActive( HandleType handleType, bool active )
1840 mImpl->mHandle[handleType].active = active;
1844 if( ( LEFT_SELECTION_HANDLE == handleType ) || ( RIGHT_SELECTION_HANDLE == handleType ) )
1846 mImpl->mHandlePreviousCrossed = false;
1849 // TODO: this is a work-around.
1850 // The problem is the handle actor does not receive the touch event with the Interrupt
1851 // state when the power button is pressed and the application goes to background.
1852 mImpl->mHandle[handleType].pressed = false;
1853 Image imageReleased = mImpl->mHandleImages[handleType][HANDLE_IMAGE_RELEASED];
1854 ImageView imageView = mImpl->mHandle[handleType].actor;
1855 if( imageReleased && imageView )
1857 imageView.SetImage( imageReleased );
1863 bool Decorator::IsHandleActive( HandleType handleType ) const
1865 return mImpl->mHandle[handleType].active ;
1868 void Decorator::SetHandleImage( HandleType handleType, HandleImageType handleImageType, Dali::Image image )
1870 mImpl->SetHandleImage( handleType, handleImageType, image );
1873 Dali::Image Decorator::GetHandleImage( HandleType handleType, HandleImageType handleImageType ) const
1875 return mImpl->mHandleImages[handleType][handleImageType];
1878 void Decorator::SetHandleColor( const Vector4& color )
1880 mImpl->mHandleColor = color;
1883 const Vector4& Decorator::GetHandleColor() const
1885 return mImpl->mHandleColor;
1888 void Decorator::SetPosition( HandleType handleType, float x, float y, float height )
1890 // Adjust handle's displacement
1891 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1893 handle.grabDisplacementX -= x - handle.position.x;
1894 handle.grabDisplacementY -= y - handle.position.y;
1896 handle.position.x = x;
1897 handle.position.y = y;
1898 handle.lineHeight = height;
1901 void Decorator::GetPosition( HandleType handleType, float& x, float& y, float& height ) const
1903 Impl::HandleImpl& handle = mImpl->mHandle[handleType];
1905 x = handle.position.x;
1906 y = handle.position.y;
1907 height = handle.lineHeight;
1910 const Vector2& Decorator::GetPosition( HandleType handleType ) const
1912 return mImpl->mHandle[handleType].position;
1915 void Decorator::FlipHandleVertically( HandleType handleType, bool flip )
1917 mImpl->mHandle[handleType].verticallyFlippedPreferred = flip;
1920 bool Decorator::IsHandleVerticallyFlipped( HandleType handleType ) const
1922 return mImpl->mHandle[handleType].verticallyFlippedPreferred;
1925 void Decorator::FlipSelectionHandlesOnCrossEnabled( bool enable )
1927 mImpl->mFlipSelectionHandlesOnCross = enable;
1930 void Decorator::SetSelectionHandleFlipState( bool indicesSwapped, bool left, bool right )
1932 mImpl->mHandleCurrentCrossed = indicesSwapped;
1933 mImpl->mFlipLeftSelectionHandleDirection = left;
1934 mImpl->mFlipRightSelectionHandleDirection = right;
1937 void Decorator::AddHighlight( float x1, float y1, float x2, float y2 )
1939 mImpl->mHighlightQuadList.push_back( QuadCoordinates(x1, y1, x2, y2) );
1942 void Decorator::ClearHighlights()
1944 mImpl->mHighlightQuadList.clear();
1945 mImpl->mHighlightPosition = Vector2::ZERO;
1948 void Decorator::SetHighlightColor( const Vector4& color )
1950 mImpl->mHighlightColor = color;
1953 const Vector4& Decorator::GetHighlightColor() const
1955 return mImpl->mHighlightColor;
1958 void Decorator::SetTextDepth( int textDepth )
1960 mImpl->mTextDepth = textDepth;
1963 void Decorator::SetPopupActive( bool active )
1965 mImpl->mActiveCopyPastePopup = active;
1968 bool Decorator::IsPopupActive() const
1970 return mImpl->mActiveCopyPastePopup ;
1973 void Decorator::SetEnabledPopupButtons( TextSelectionPopup::Buttons& enabledButtonsBitMask )
1975 mImpl->mEnabledPopupButtons = enabledButtonsBitMask;
1977 if ( !mImpl->mCopyPastePopup.actor )
1979 mImpl->mCopyPastePopup.actor = TextSelectionPopup::New( &mImpl->mTextSelectionPopupCallbackInterface );
1980 #ifdef DECORATOR_DEBUG
1981 mImpl->mCopyPastePopup.actor.SetName("mCopyPastePopup");
1983 mImpl->mCopyPastePopup.actor.SetAnchorPoint( AnchorPoint::CENTER );
1984 mImpl->mCopyPastePopup.actor.OnRelayoutSignal().Connect( mImpl, &Decorator::Impl::PopupRelayoutComplete ); // Position popup after size negotiation
1987 mImpl->mCopyPastePopup.actor.EnableButtons( mImpl->mEnabledPopupButtons );
1990 TextSelectionPopup::Buttons& Decorator::GetEnabledPopupButtons()
1992 return mImpl->mEnabledPopupButtons;
1997 void Decorator::SetScrollThreshold( float threshold )
1999 mImpl->SetScrollThreshold( threshold );
2002 float Decorator::GetScrollThreshold() const
2004 return mImpl->GetScrollThreshold();
2007 void Decorator::SetScrollSpeed( float speed )
2009 mImpl->SetScrollSpeed( speed );
2012 float Decorator::GetScrollSpeed() const
2014 return mImpl->GetScrollSpeed();
2017 void Decorator::NotifyEndOfScroll()
2019 mImpl->NotifyEndOfScroll();
2022 Decorator::~Decorator()
2027 Decorator::Decorator( ControllerInterface& controller,
2028 TextSelectionPopupCallbackInterface& callbackInterface )
2031 mImpl = new Decorator::Impl( controller, callbackInterface );
2036 } // namespace Toolkit