2 * Copyright (c) 2016 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/controls/tooltip/tooltip.h>
24 #include <dali/public-api/events/hover-event.h>
25 #include <dali/public-api/adaptor-framework/timer.h>
26 #include <dali/public-api/common/stage.h>
27 #include <dali/devel-api/scripting/enum-helper.h>
30 #include <dali-toolkit/public-api/controls/table-view/table-view.h>
31 #include <dali-toolkit/public-api/controls/text-controls/text-label.h>
32 #include <dali-toolkit/public-api/visuals/visual-properties.h>
33 #include <dali-toolkit/devel-api/controls/tooltip/tooltip-properties.h>
34 #include <dali-toolkit/devel-api/visuals/text-visual-properties.h>
35 #include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
36 #include <dali-toolkit/internal/controls/popup/popup-impl.h>
37 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
51 DALI_ENUM_TO_STRING_TABLE_BEGIN( TOOLTIP_POSITION )
52 DALI_ENUM_TO_STRING_WITH_SCOPE( Toolkit::Tooltip::Position, ABOVE )
53 DALI_ENUM_TO_STRING_WITH_SCOPE( Toolkit::Tooltip::Position, BELOW )
54 DALI_ENUM_TO_STRING_WITH_SCOPE( Toolkit::Tooltip::Position, HOVER_POINT )
55 DALI_ENUM_TO_STRING_TABLE_END( TOOLTIP_POSITION )
57 const float MILLISECONDS_PER_SECOND = 1000.0f;
59 const char * const PROPERTY_CONTENT_NAME = "content";
60 const char * const PROPERTY_LAYOUT_NAME = "layout";
61 const char * const PROPERTY_WAIT_TIME_NAME = "waitTime";
62 const char * const PROPERTY_BACKGROUND_NAME = "background";
63 const char * const PROPERTY_TAIL_NAME = "tail";
64 const char * const PROPERTY_POSITION_NAME = "position";
65 const char * const PROPERTY_HOVER_POINT_OFFSET_NAME = "hoverPointOffset";
66 const char * const PROPERTY_MOVEMENT_THRESHOLD = "movementThreshold";
67 const char * const PROPERTY_DISAPPEAR_ON_MOVEMENT = "disappearOnMovement";
69 const char * const PROPERTY_BACKGROUND_VISUAL = "visual";
70 const char * const PROPERTY_BACKGROUND_BORDER = "border";
72 const char * const PROPERTY_TAIL_VISIBILITY = "visibility";
73 const char * const PROPERTY_TAIL_ABOVE_VISUAL = "aboveVisual";
74 const char * const PROPERTY_TAIL_BELOW_VISUAL = "belowVisual";
76 } // unnamed namespace
78 TooltipPtr Tooltip::New( Toolkit::Control control )
80 return new Tooltip( control );
83 void Tooltip::SetProperties( const Property::Value& value )
85 Toolkit::Control control = mControl.GetHandle();
88 Property::Map* properties = value.GetMap();
91 const Property::Map::SizeType count = properties->Count();
92 for( Property::Map::SizeType position = 0; position < count; ++position )
94 KeyValuePair keyValue = properties->GetKeyValue( position );
95 Property::Key& key = keyValue.first;
96 Property::Value& value = keyValue.second;
98 if( key == Toolkit::Tooltip::Property::CONTENT || key == PROPERTY_CONTENT_NAME )
100 SetContent( control, value );
102 else if( key == Toolkit::Tooltip::Property::LAYOUT || key == PROPERTY_LAYOUT_NAME )
104 value.Get( mLayout );
106 else if( key == Toolkit::Tooltip::Property::WAIT_TIME || key == PROPERTY_WAIT_TIME_NAME )
108 float waitTime = 0.0f;
109 if( value.Get( waitTime ) )
111 mWaitTime = waitTime * MILLISECONDS_PER_SECOND;
114 else if( key == Toolkit::Tooltip::Property::BACKGROUND || key == PROPERTY_BACKGROUND_NAME )
116 SetBackground( value );
118 else if( key == Toolkit::Tooltip::Property::TAIL || key == PROPERTY_TAIL_NAME )
122 else if( key == Toolkit::Tooltip::Property::POSITION || key == PROPERTY_POSITION_NAME )
124 Scripting::GetEnumerationProperty< Toolkit::Tooltip::Position::Type >( value, TOOLTIP_POSITION_TABLE, TOOLTIP_POSITION_TABLE_COUNT, mPositionType );
126 else if( key == Toolkit::Tooltip::Property::HOVER_POINT_OFFSET || key == PROPERTY_HOVER_POINT_OFFSET_NAME )
128 value.Get( mHoverPointOffset );
130 else if( key == Toolkit::Tooltip::Property::MOVEMENT_THRESHOLD || key == PROPERTY_MOVEMENT_THRESHOLD )
132 value.Get( mMovementThreshold );
134 else if( key == Toolkit::Tooltip::Property::DISAPPEAR_ON_MOVEMENT || key == PROPERTY_DISAPPEAR_ON_MOVEMENT )
136 value.Get( mDisappearOnMovement );
142 Property::Type type = value.GetType();
143 if( ( value.GetType() == Property::STRING ) || ( type == Property::ARRAY ) )
145 SetContent( control, value );
151 void Tooltip::CreatePropertyMap( Property::Map& map ) const
153 if( ! mContentTextVisual.Empty() )
155 Property::Map content = mContentTextVisual; // Need this copy as there's no Value constructor which takes in a 'const Property::Map&'.
156 map.Insert( Toolkit::Tooltip::Property::CONTENT, content );
158 else if( ! mContentArray.Empty() )
160 Property::Array content = mContentArray; // Need this copy as there's no Value constructor which takes in a 'const Property::Array&'.
161 map.Insert( Toolkit::Tooltip::Property::CONTENT, content );
164 map.Insert( Toolkit::Tooltip::Property::LAYOUT, mLayout );
165 map.Insert( Toolkit::Tooltip::Property::WAIT_TIME, static_cast<float>( mWaitTime ) / MILLISECONDS_PER_SECOND );
166 map.Insert( Toolkit::Tooltip::Property::BACKGROUND,
167 Property::Map().Add( Toolkit::Tooltip::Background::Property::VISUAL, mBackgroundImage )
168 .Add( Toolkit::Tooltip::Background::Property::BORDER, mBackgroundBorder ) );
169 map.Insert( Toolkit::Tooltip::Property::TAIL,
170 Property::Map().Add( Toolkit::Tooltip::Tail::Property::VISIBILITY, mTailVisibility )
171 .Add( Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL, mTailImages[ Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL ])
172 .Add( Toolkit::Tooltip::Tail::Property::BELOW_VISUAL, mTailImages[ Toolkit::Tooltip::Tail::Property::BELOW_VISUAL ]) );
173 map.Insert( Toolkit::Tooltip::Property::POSITION, mPositionType );
174 map.Insert( Toolkit::Tooltip::Property::HOVER_POINT_OFFSET, mHoverPointOffset );
175 map.Insert( Toolkit::Tooltip::Property::MOVEMENT_THRESHOLD, mMovementThreshold );
176 map.Insert( Toolkit::Tooltip::Property::DISAPPEAR_ON_MOVEMENT, mDisappearOnMovement );
179 Tooltip::Tooltip( Toolkit::Control control )
183 mContentTextVisual(),
186 mBackgroundBorder( 0, 0, 0, 0 ),
189 mHoverPointOffset( 10.0f, 10.0f ),
191 mMovementThreshold( 5.0f ),
193 mPositionType( Toolkit::Tooltip::Position::ABOVE ),
194 mTailVisibility( false ),
195 mDisappearOnMovement( false ),
196 mSignalsConnected( false )
198 mTailImages[ Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL ] = "";
199 mTailImages[ Toolkit::Tooltip::Tail::Property::BELOW_VISUAL ] = "";
211 void Tooltip::SetContent( Toolkit::Control& control, const Property::Value& value )
213 // Delete popup & timer
217 mTooltipTimer.Stop();
218 mTooltipTimer.Reset();
227 bool connectSignals = false;
229 Property::Type type = value.GetType();
230 if( type == Property::MAP )
232 Property::Map* map = value.GetMap();
235 mContentTextVisual.Merge( *map );
237 Property::Value* typeValue = map->Find( Toolkit::Visual::Property::TYPE, VISUAL_TYPE );
240 // Set to an invalid value so it definitely changes if set in Scripting::GetEnumerationProperty
241 Toolkit::DevelVisual::Type visualType = static_cast< Toolkit::DevelVisual::Type >( -1 );
243 if( Scripting::GetEnumerationProperty( *typeValue, VISUAL_TYPE_TABLE, VISUAL_TYPE_TABLE_COUNT, visualType ) )
245 if( visualType == Toolkit::DevelVisual::TEXT )
247 // Visual Type is text, ensure we have a the TEXT property set before we connect to the signals.
249 if( map->Find( Toolkit::TextVisual::Property::TEXT, TEXT_PROPERTY ) )
251 mContentArray.Clear();
252 connectSignals = true;
257 // Visual Type is not text, so connect to the signals as we're displaying a non text visual.
259 mContentArray.Clear();
260 connectSignals = true;
266 else if( type == Property::ARRAY )
268 if( value.Get( mContentArray ) )
270 mContentTextVisual.Clear();
271 connectSignals = true;
274 else if( type == Property::STRING )
277 if( value.Get( text ) )
279 mContentTextVisual[ Toolkit::TextVisual::Property::TEXT ] = text;
280 mContentTextVisual[ Toolkit::Visual::Property::TYPE ] = DevelVisual::TEXT;
281 mContentArray.Clear();
282 connectSignals = true;
286 if( connectSignals && ! mSignalsConnected )
288 control.HoveredSignal().Connect( this, &Tooltip::OnHovered );
289 control.SetLeaveRequired( true );
290 mSignalsConnected = true;
294 void Tooltip::SetBackground( const Property::Value& value )
296 Property::Type type = value.GetType();
298 if( type == Property::STRING )
300 value.Get( mBackgroundImage );
301 mBackgroundBorder.Set( 0, 0, 0, 0 );
303 else if( type == Property::MAP )
305 Property::Map* map = value.GetMap();
308 const Property::Map::SizeType count = map->Count();
309 for( Property::Map::SizeType position = 0; position < count; ++position )
311 KeyValuePair keyValue = map->GetKeyValue( position );
312 Property::Key& key = keyValue.first;
313 Property::Value& value = keyValue.second;
315 if( key == Toolkit::Tooltip::Background::Property::VISUAL || key == PROPERTY_BACKGROUND_VISUAL )
317 value.Get( mBackgroundImage );
319 else if( key == Toolkit::Tooltip::Background::Property::BORDER || key == PROPERTY_BACKGROUND_BORDER )
321 if( ! value.Get( mBackgroundBorder ) )
323 // If not a Property::RECTANGLE, then check if it's a Vector4 and set it accordingly
324 Vector4 valueVector4;
325 if( value.Get( valueVector4 ) )
327 mBackgroundBorder.left = valueVector4.x;
328 mBackgroundBorder.right = valueVector4.y;
329 mBackgroundBorder.bottom = valueVector4.z;
330 mBackgroundBorder.top = valueVector4.w;
339 void Tooltip::SetTail( const Property::Value& value )
341 Property::Type type = value.GetType();
343 if( type == Property::BOOLEAN )
345 value.Get( mTailVisibility );
347 else if( type == Property::MAP )
350 if( value.Get( map ) )
352 const Property::Map::SizeType count = map.Count();
353 for( Property::Map::SizeType position = 0; position < count; ++position )
355 KeyValuePair keyValue = map.GetKeyValue( position );
356 Property::Key& key = keyValue.first;
357 Property::Value& value = keyValue.second;
359 // Set the values manually rather than merging so that we only have to deal with Property indices when creating the actual tooltip.
361 if( key == Toolkit::Tooltip::Tail::Property::VISIBILITY || key == PROPERTY_TAIL_VISIBILITY )
363 value.Get( mTailVisibility );
365 else if( key == Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL || key == PROPERTY_TAIL_ABOVE_VISUAL )
368 if( value.Get( path ) )
370 mTailImages[ Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL ] = path;
373 else if( key == Toolkit::Tooltip::Tail::Property::BELOW_VISUAL || key == PROPERTY_TAIL_BELOW_VISUAL )
376 if( value.Get( path ) )
378 mTailImages[ Toolkit::Tooltip::Tail::Property::BELOW_VISUAL ] = path;
386 bool Tooltip::OnHovered( Actor /* actor */, const HoverEvent& hover )
388 const TouchPoint::State state = hover.points[0].state;
391 case TouchPoint::Started:
392 case TouchPoint::Motion:
396 if( ! mTooltipTimer )
398 mHoverPoint = hover.points[ 0 ].screen;
399 mTooltipTimer = Timer::New( mWaitTime );
400 mTooltipTimer.TickSignal().Connect( this, &Tooltip::OnTimeout );
401 mTooltipTimer.Start();
405 Vector2 movement = mHoverPoint - hover.points[ 0 ].screen;
406 if( std::abs( movement.Length() ) > mMovementThreshold )
408 mTooltipTimer.Stop();
409 mTooltipTimer.Reset();
411 mHoverPoint = hover.points[ 0 ].screen;
412 mTooltipTimer = Timer::New( mWaitTime );
413 mTooltipTimer.TickSignal().Connect( this, &Tooltip::OnTimeout );
414 mTooltipTimer.Start();
418 else if( mDisappearOnMovement )
420 // Popup is showing, and we're set to disappear on excessive movement so make sure we're still within the threshold.
422 Vector2 movement = mHoverPoint - hover.points[ 0 ].screen;
423 if( std::abs( movement.Length() ) > mMovementThreshold )
425 // Exceeding the threshold, hide the popup.
429 mTooltipTimer.Stop();
430 mTooltipTimer.Reset();
441 case TouchPoint::Finished:
442 case TouchPoint::Leave:
443 case TouchPoint::Interrupted:
447 mTooltipTimer.Stop();
448 mTooltipTimer.Reset();
458 case TouchPoint::Stationary:
459 case TouchPoint::Last:
468 bool Tooltip::OnTimeout()
470 Toolkit::Control control = mControl.GetHandle();
471 if( ! mPopup && control )
473 mPopup = Toolkit::Popup::New();
475 // General set up of popup
476 mPopup.SetResizePolicy( ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS );
477 mPopup.SetProperty( Toolkit::Popup::Property::CONTEXTUAL_MODE, "NON_CONTEXTUAL" );
478 mPopup.SetProperty( Toolkit::Popup::Property::ANIMATION_MODE, "NONE" );
479 mPopup.SetProperty( Toolkit::Popup::Property::BACKING_ENABLED, false ); // Disable the dimmed backing.
480 mPopup.SetProperty( Toolkit::Popup::Property::TOUCH_TRANSPARENT, true ); // Let events pass through the popup
481 mPopup.SetParentOrigin( ParentOrigin::TOP_LEFT );
482 mPopup.SetAnchorPoint( AnchorPoint::TOP_LEFT );
485 mPopup.SetProperty( Toolkit::Popup::Property::POPUP_BACKGROUND_IMAGE, mBackgroundImage );
486 mPopup.SetProperty( Toolkit::Popup::Property::POPUP_BACKGROUND_BORDER, mBackgroundBorder );
489 mPopup.SetProperty( Toolkit::Popup::Property::TAIL_VISIBILITY, mTailVisibility );
490 mPopup.SetProperty( Toolkit::Popup::Property::TAIL_UP_IMAGE, mTailImages[ Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL ] );
491 mPopup.SetProperty( Toolkit::Popup::Property::TAIL_DOWN_IMAGE, mTailImages[ Toolkit::Tooltip::Tail::Property::BELOW_VISUAL ] );
493 Vector3 tailPosition;
494 switch( mPositionType )
496 case Toolkit::Tooltip::Position::HOVER_POINT:
497 case Toolkit::Tooltip::Position::BELOW:
499 tailPosition = Vector3( 0.5f, 0.0f, 0.0 );
503 case Toolkit::Tooltip::Position::ABOVE:
505 tailPosition = Vector3( 0.5f, 1.0f, 0.0 );
509 mPopup.SetProperty( Toolkit::Popup::Property::TAIL_POSITION, tailPosition );
513 if( ! mContentTextVisual.Empty() )
515 content = Toolkit::Control::New();
516 content.SetProperty( Toolkit::Control::Property::BACKGROUND, mContentTextVisual );
518 else if( ! mContentArray.Empty() )
520 const unsigned int visuals = mContentArray.Size();
521 unsigned int rows = mLayout.x;
522 unsigned int columns = mLayout.y;
523 if( Equals( mLayout.x, 1.0f, Math::MACHINE_EPSILON_1 ) &&
524 Equals( mLayout.y, 1.0f, Math::MACHINE_EPSILON_1 ) &&
531 Toolkit::TableView tableView = Toolkit::TableView::New( rows, columns );
532 tableView.SetResizePolicy( ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS );
534 for( unsigned int currentContent = 0, currentRow = 0; currentRow < rows && currentContent < visuals; ++currentRow )
536 tableView.SetFitHeight( currentRow );
537 for( unsigned int currentColumn = 0; currentColumn < columns && currentContent < visuals; ++currentColumn )
539 Actor child = Toolkit::Control::New();
540 child.SetProperty( Toolkit::Control::Property::BACKGROUND, mContentArray[ currentContent ] );
542 Toolkit::TableView::CellPosition cellPosition( currentRow, currentColumn );
543 tableView.AddChild( child, cellPosition );
544 tableView.SetCellAlignment( cellPosition, HorizontalAlignment::CENTER, VerticalAlignment::CENTER );
545 tableView.SetFitWidth( currentColumn );
552 mPopup.SetContent( content );
554 // Connect to the relayout signal of the background of the popup as at that point we have the full size
555 Actor popupBackground = GetImpl( mPopup ).GetPopupBackgroundImage();
556 if( popupBackground )
558 popupBackground.OnRelayoutSignal().Connect( this, &Tooltip::OnRelayout );
561 mPopup.SetDisplayState( Toolkit::Popup::SHOWN );
563 Stage::GetCurrent().Add( mPopup );
569 void Tooltip::OnRelayout( Actor actor )
571 if( mPopup && actor )
573 float popupWidth = actor.GetRelayoutSize( Dimension::WIDTH );
574 float popupHeight = actor.GetRelayoutSize( Dimension::HEIGHT );
575 float tailHeight = 0.0f;
578 if( mTailVisibility )
580 // Popup's background has the tail, we want to know the tail size as well.
581 if( actor.GetChildCount() )
583 tail = actor.GetChildAt( 0 );
586 tailHeight = tail.GetRelayoutSize( Dimension::HEIGHT );
591 Vector2 stageSize = Stage::GetCurrent().GetSize();
594 switch( mPositionType )
596 case Toolkit::Tooltip::Position::HOVER_POINT:
598 position = mHoverPoint + mHoverPointOffset;
599 position.y += tailHeight;
603 case Toolkit::Tooltip::Position::ABOVE:
605 Toolkit::Control control = mControl.GetHandle();
608 Vector3 worldPos = control.GetCurrentWorldPosition();
609 float height = control.GetRelayoutSize( Dimension::HEIGHT );
611 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
612 position.y = stageSize.height * 0.5f + worldPos.y - height * 0.5f - popupHeight * 1.0f - tailHeight;
617 case Toolkit::Tooltip::Position::BELOW:
619 Toolkit::Control control = mControl.GetHandle();
622 Vector3 worldPos = control.GetCurrentWorldPosition();
623 float height = control.GetRelayoutSize( Dimension::HEIGHT );
625 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
626 position.y = stageSize.height * 0.5f + worldPos.y + height * 0.5f + tailHeight;
632 // Ensure the Popup is still on the screen
634 if( position.x < 0.0f )
638 else if( ( position.x + popupWidth ) > stageSize.width )
640 position.x -= position.x + popupWidth - stageSize.width;
643 bool yPosChanged = false;
644 if( position.y < 0.0f )
649 else if( ( position.y + popupHeight ) > stageSize.height )
652 position.y -= position.y + popupHeight - stageSize.height;
655 if( yPosChanged && tail )
657 // If we change the y position, then the tail may be shown pointing to the wrong control so just hide it.
658 tail.SetVisible( false );
661 mPopup.SetPosition( position );
665 } // namespace Internal
667 } // namespace Toolkit