2 * Copyright (c) 2021 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/devel-api/common/stage.h>
25 #include <dali/devel-api/scripting/enum-helper.h>
26 #include <dali/public-api/adaptor-framework/timer.h>
27 #include <dali/public-api/events/hover-event.h>
30 #include <dali-toolkit/devel-api/controls/table-view/table-view.h>
31 #include <dali-toolkit/devel-api/controls/tooltip/tooltip-properties.h>
32 #include <dali-toolkit/internal/controls/popup/popup-impl.h>
33 #include <dali-toolkit/internal/visuals/visual-string-constants.h>
34 #include <dali-toolkit/public-api/controls/text-controls/text-label.h>
35 #include <dali-toolkit/public-api/visuals/text-visual-properties.h>
36 #include <dali-toolkit/public-api/visuals/visual-properties.h>
46 DALI_ENUM_TO_STRING_TABLE_BEGIN(TOOLTIP_POSITION)
47 DALI_ENUM_TO_STRING_WITH_SCOPE(Toolkit::Tooltip::Position, ABOVE)
48 DALI_ENUM_TO_STRING_WITH_SCOPE(Toolkit::Tooltip::Position, BELOW)
49 DALI_ENUM_TO_STRING_WITH_SCOPE(Toolkit::Tooltip::Position, HOVER_POINT)
50 DALI_ENUM_TO_STRING_TABLE_END(TOOLTIP_POSITION)
52 const float MILLISECONDS_PER_SECOND = 1000.0f;
54 const char* const PROPERTY_CONTENT_NAME = "content";
55 const char* const PROPERTY_LAYOUT_NAME = "layout";
56 const char* const PROPERTY_WAIT_TIME_NAME = "waitTime";
57 const char* const PROPERTY_BACKGROUND_NAME = "background";
58 const char* const PROPERTY_TAIL_NAME = "tail";
59 const char* const PROPERTY_POSITION_NAME = "position";
60 const char* const PROPERTY_HOVER_POINT_OFFSET_NAME = "hoverPointOffset";
61 const char* const PROPERTY_MOVEMENT_THRESHOLD = "movementThreshold";
62 const char* const PROPERTY_DISAPPEAR_ON_MOVEMENT = "disappearOnMovement";
64 const char* const PROPERTY_BACKGROUND_VISUAL = "visual";
65 const char* const PROPERTY_BACKGROUND_BORDER = "border";
67 const char* const PROPERTY_TAIL_VISIBILITY = "visibility";
68 const char* const PROPERTY_TAIL_ABOVE_VISUAL = "aboveVisual";
69 const char* const PROPERTY_TAIL_BELOW_VISUAL = "belowVisual";
71 } // unnamed namespace
73 TooltipPtr Tooltip::New(Toolkit::Control control)
75 return new Tooltip(control);
78 void Tooltip::SetProperties(const Property::Value& value)
80 Toolkit::Control control = mControl.GetHandle();
83 const Property::Map* properties = value.GetMap();
86 const Property::Map::SizeType count = properties->Count();
87 for(Property::Map::SizeType position = 0; position < count; ++position)
89 KeyValuePair keyValue = properties->GetKeyValue(position);
90 Property::Key& key = keyValue.first;
91 Property::Value& value = keyValue.second;
93 if(key == Toolkit::Tooltip::Property::CONTENT || key == PROPERTY_CONTENT_NAME)
95 SetContent(control, value);
97 else if(key == Toolkit::Tooltip::Property::LAYOUT || key == PROPERTY_LAYOUT_NAME)
101 else if(key == Toolkit::Tooltip::Property::WAIT_TIME || key == PROPERTY_WAIT_TIME_NAME)
103 float waitTime = 0.0f;
104 if(value.Get(waitTime))
106 mWaitTime = waitTime * MILLISECONDS_PER_SECOND;
109 else if(key == Toolkit::Tooltip::Property::BACKGROUND || key == PROPERTY_BACKGROUND_NAME)
111 SetBackground(value);
113 else if(key == Toolkit::Tooltip::Property::TAIL || key == PROPERTY_TAIL_NAME)
117 else if(key == Toolkit::Tooltip::Property::POSITION || key == PROPERTY_POSITION_NAME)
119 Scripting::GetEnumerationProperty<Toolkit::Tooltip::Position::Type>(value, TOOLTIP_POSITION_TABLE, TOOLTIP_POSITION_TABLE_COUNT, mPositionType);
121 else if(key == Toolkit::Tooltip::Property::HOVER_POINT_OFFSET || key == PROPERTY_HOVER_POINT_OFFSET_NAME)
123 value.Get(mHoverPointOffset);
125 else if(key == Toolkit::Tooltip::Property::MOVEMENT_THRESHOLD || key == PROPERTY_MOVEMENT_THRESHOLD)
127 value.Get(mMovementThreshold);
129 else if(key == Toolkit::Tooltip::Property::DISAPPEAR_ON_MOVEMENT || key == PROPERTY_DISAPPEAR_ON_MOVEMENT)
131 value.Get(mDisappearOnMovement);
137 Property::Type type = value.GetType();
138 if((value.GetType() == Property::STRING) || (type == Property::ARRAY))
140 SetContent(control, value);
146 void Tooltip::CreatePropertyMap(Property::Map& map) const
148 if(!mContentTextVisual.Empty())
150 Property::Map content = mContentTextVisual; // Need this copy as there's no Value constructor which takes in a 'const Property::Map&'.
151 map.Insert(Toolkit::Tooltip::Property::CONTENT, content);
153 else if(!mContentArray.Empty())
155 Property::Array content = mContentArray; // Need this copy as there's no Value constructor which takes in a 'const Property::Array&'.
156 map.Insert(Toolkit::Tooltip::Property::CONTENT, content);
159 map.Insert(Toolkit::Tooltip::Property::LAYOUT, mLayout);
160 map.Insert(Toolkit::Tooltip::Property::WAIT_TIME, static_cast<float>(mWaitTime) / MILLISECONDS_PER_SECOND);
161 map.Insert(Toolkit::Tooltip::Property::BACKGROUND,
162 Property::Map().Add(Toolkit::Tooltip::Background::Property::VISUAL, mBackgroundImage).Add(Toolkit::Tooltip::Background::Property::BORDER, mBackgroundBorder));
163 map.Insert(Toolkit::Tooltip::Property::TAIL,
164 Property::Map().Add(Toolkit::Tooltip::Tail::Property::VISIBILITY, mTailVisibility).Add(Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL, mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL]).Add(Toolkit::Tooltip::Tail::Property::BELOW_VISUAL, mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL]));
165 map.Insert(Toolkit::Tooltip::Property::POSITION, mPositionType);
166 map.Insert(Toolkit::Tooltip::Property::HOVER_POINT_OFFSET, mHoverPointOffset);
167 map.Insert(Toolkit::Tooltip::Property::MOVEMENT_THRESHOLD, mMovementThreshold);
168 map.Insert(Toolkit::Tooltip::Property::DISAPPEAR_ON_MOVEMENT, mDisappearOnMovement);
171 Tooltip::Tooltip(Toolkit::Control control)
175 mContentTextVisual(),
178 mBackgroundBorder(0, 0, 0, 0),
181 mHoverPointOffset(10.0f, 10.0f),
183 mMovementThreshold(5.0f),
185 mPositionType(Toolkit::Tooltip::Position::ABOVE),
186 mTailVisibility(false),
187 mDisappearOnMovement(false),
188 mSignalsConnected(false)
190 mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL] = "";
191 mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL] = "";
203 void Tooltip::SetContent(Toolkit::Control& control, const Property::Value& value)
205 // Delete popup & timer
209 mTooltipTimer.Stop();
210 mTooltipTimer.Reset();
219 bool connectSignals = false;
221 Property::Type type = value.GetType();
222 if(type == Property::MAP)
224 const Property::Map* map = value.GetMap();
227 mContentTextVisual.Merge(*map);
229 Property::Value* typeValue = map->Find(Toolkit::Visual::Property::TYPE, VISUAL_TYPE);
232 // Set to an invalid value so it definitely changes if set in Scripting::GetEnumerationProperty
233 Toolkit::Visual::Type visualType = static_cast<Toolkit::Visual::Type>(-1);
235 if(Scripting::GetEnumerationProperty(*typeValue, VISUAL_TYPE_TABLE, VISUAL_TYPE_TABLE_COUNT, visualType))
237 if(visualType == Toolkit::Visual::TEXT)
239 // Visual Type is text, ensure we have a the TEXT property set before we connect to the signals.
241 if(map->Find(Toolkit::TextVisual::Property::TEXT, TEXT_PROPERTY))
243 mContentArray.Clear();
244 connectSignals = true;
249 // Visual Type is not text, so connect to the signals as we're displaying a non text visual.
251 mContentArray.Clear();
252 connectSignals = true;
258 else if(type == Property::ARRAY)
260 if(value.Get(mContentArray))
262 mContentTextVisual.Clear();
263 connectSignals = true;
266 else if(type == Property::STRING)
271 mContentTextVisual[Toolkit::TextVisual::Property::TEXT] = text;
272 mContentTextVisual[Toolkit::Visual::Property::TYPE] = Toolkit::Visual::TEXT;
273 mContentArray.Clear();
274 connectSignals = true;
278 if(connectSignals && !mSignalsConnected)
280 control.HoveredSignal().Connect(this, &Tooltip::OnHovered);
281 control.SetProperty(Actor::Property::LEAVE_REQUIRED, true);
282 mSignalsConnected = true;
286 void Tooltip::SetBackground(const Property::Value& value)
288 Property::Type type = value.GetType();
290 if(type == Property::STRING)
292 value.Get(mBackgroundImage);
293 mBackgroundBorder.Set(0, 0, 0, 0);
295 else if(type == Property::MAP)
297 const Property::Map* map = value.GetMap();
300 const Property::Map::SizeType count = map->Count();
301 for(Property::Map::SizeType position = 0; position < count; ++position)
303 KeyValuePair keyValue = map->GetKeyValue(position);
304 Property::Key& key = keyValue.first;
305 Property::Value& value = keyValue.second;
307 if(key == Toolkit::Tooltip::Background::Property::VISUAL || key == PROPERTY_BACKGROUND_VISUAL)
309 value.Get(mBackgroundImage);
311 else if(key == Toolkit::Tooltip::Background::Property::BORDER || key == PROPERTY_BACKGROUND_BORDER)
313 if(!value.Get(mBackgroundBorder))
315 // If not a Property::RECTANGLE, then check if it's a Vector4 and set it accordingly
316 Vector4 valueVector4;
317 if(value.Get(valueVector4))
319 mBackgroundBorder.left = valueVector4.x;
320 mBackgroundBorder.right = valueVector4.y;
321 mBackgroundBorder.bottom = valueVector4.z;
322 mBackgroundBorder.top = valueVector4.w;
331 void Tooltip::SetTail(const Property::Value& value)
333 Property::Type type = value.GetType();
335 if(type == Property::BOOLEAN)
337 value.Get(mTailVisibility);
339 else if(type == Property::MAP)
344 const Property::Map::SizeType count = map.Count();
345 for(Property::Map::SizeType position = 0; position < count; ++position)
347 KeyValuePair keyValue = map.GetKeyValue(position);
348 Property::Key& key = keyValue.first;
349 Property::Value& value = keyValue.second;
351 // Set the values manually rather than merging so that we only have to deal with Property indices when creating the actual tooltip.
353 if(key == Toolkit::Tooltip::Tail::Property::VISIBILITY || key == PROPERTY_TAIL_VISIBILITY)
355 value.Get(mTailVisibility);
357 else if(key == Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL || key == PROPERTY_TAIL_ABOVE_VISUAL)
362 mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL] = path;
365 else if(key == Toolkit::Tooltip::Tail::Property::BELOW_VISUAL || key == PROPERTY_TAIL_BELOW_VISUAL)
370 mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL] = path;
378 bool Tooltip::OnHovered(Actor /* actor */, const HoverEvent& hover)
380 const PointState::Type state = hover.GetState(0);
383 case PointState::STARTED:
384 case PointState::MOTION:
390 mHoverPoint = hover.GetScreenPosition(0);
391 mTooltipTimer = Timer::New(mWaitTime);
392 mTooltipTimer.TickSignal().Connect(this, &Tooltip::OnTimeout);
393 mTooltipTimer.Start();
397 Vector2 movement = mHoverPoint - hover.GetScreenPosition(0);
398 if(std::abs(movement.Length()) > mMovementThreshold)
400 mTooltipTimer.Stop();
401 mTooltipTimer.Reset();
403 mHoverPoint = hover.GetScreenPosition(0);
404 mTooltipTimer = Timer::New(mWaitTime);
405 mTooltipTimer.TickSignal().Connect(this, &Tooltip::OnTimeout);
406 mTooltipTimer.Start();
410 else if(mDisappearOnMovement)
412 // Popup is showing, and we're set to disappear on excessive movement so make sure we're still within the threshold.
414 Vector2 movement = mHoverPoint - hover.GetScreenPosition(0);
415 if(std::abs(movement.Length()) > mMovementThreshold)
417 // Exceeding the threshold, hide the popup.
421 mTooltipTimer.Stop();
422 mTooltipTimer.Reset();
433 case PointState::FINISHED:
434 case PointState::LEAVE:
435 case PointState::INTERRUPTED:
439 mTooltipTimer.Stop();
440 mTooltipTimer.Reset();
450 case PointState::STATIONARY:
459 bool Tooltip::OnTimeout()
461 Toolkit::Control control = mControl.GetHandle();
462 if(!mPopup && control)
464 mPopup = Toolkit::Popup::New();
466 // General set up of popup
467 mPopup.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS);
468 mPopup.SetProperty(Toolkit::Popup::Property::CONTEXTUAL_MODE, "NON_CONTEXTUAL");
469 mPopup.SetProperty(Toolkit::Popup::Property::ANIMATION_MODE, "NONE");
470 mPopup.SetProperty(Toolkit::Popup::Property::BACKING_ENABLED, false); // Disable the dimmed backing.
471 mPopup.SetProperty(Toolkit::Popup::Property::TOUCH_TRANSPARENT, true); // Let events pass through the popup
472 mPopup.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
473 mPopup.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
476 mPopup.SetProperty(Toolkit::Popup::Property::POPUP_BACKGROUND_IMAGE, mBackgroundImage);
477 mPopup.SetProperty(Toolkit::Popup::Property::POPUP_BACKGROUND_BORDER, mBackgroundBorder);
480 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_VISIBILITY, mTailVisibility);
481 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_UP_IMAGE, mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL]);
482 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_DOWN_IMAGE, mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL]);
484 Vector3 tailPosition;
485 switch(mPositionType)
487 case Toolkit::Tooltip::Position::HOVER_POINT:
488 case Toolkit::Tooltip::Position::BELOW:
490 tailPosition = Vector3(0.5f, 0.0f, 0.0);
494 case Toolkit::Tooltip::Position::ABOVE:
496 tailPosition = Vector3(0.5f, 1.0f, 0.0);
500 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_POSITION, tailPosition);
504 if(!mContentTextVisual.Empty())
506 content = Toolkit::Control::New();
507 content.SetProperty(Toolkit::Control::Property::BACKGROUND, mContentTextVisual);
509 else if(!mContentArray.Empty())
511 const unsigned int visuals = mContentArray.Size();
512 unsigned int rows = mLayout.x;
513 unsigned int columns = mLayout.y;
514 if(Equals(mLayout.x, 1.0f, Math::MACHINE_EPSILON_1) &&
515 Equals(mLayout.y, 1.0f, Math::MACHINE_EPSILON_1) &&
522 Toolkit::TableView tableView = Toolkit::TableView::New(rows, columns);
523 tableView.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS);
525 for(unsigned int currentContent = 0, currentRow = 0; currentRow < rows && currentContent < visuals; ++currentRow)
527 tableView.SetFitHeight(currentRow);
528 for(unsigned int currentColumn = 0; currentColumn < columns && currentContent < visuals; ++currentColumn)
530 Actor child = Toolkit::Control::New();
531 child.SetProperty(Toolkit::Control::Property::BACKGROUND, mContentArray[currentContent]);
533 Toolkit::TableView::CellPosition cellPosition(currentRow, currentColumn);
534 tableView.AddChild(child, cellPosition);
535 tableView.SetCellAlignment(cellPosition, HorizontalAlignment::CENTER, VerticalAlignment::CENTER);
536 tableView.SetFitWidth(currentColumn);
543 mPopup.SetContent(content);
545 // Connect to the relayout signal of the background of the popup as at that point we have the full size
546 Actor popupBackground = GetImpl(mPopup).GetPopupBackgroundImage();
549 popupBackground.OnRelayoutSignal().Connect(this, &Tooltip::OnRelayout);
552 mPopup.SetDisplayState(Toolkit::Popup::SHOWN);
554 Stage::GetCurrent().Add(mPopup);
560 void Tooltip::OnRelayout(Actor actor)
564 float popupWidth = actor.GetRelayoutSize(Dimension::WIDTH);
565 float popupHeight = actor.GetRelayoutSize(Dimension::HEIGHT);
566 float tailHeight = 0.0f;
571 // Popup's background has the tail, we want to know the tail size as well.
572 if(actor.GetChildCount())
574 tail = actor.GetChildAt(0);
577 tailHeight = tail.GetRelayoutSize(Dimension::HEIGHT);
582 Vector2 stageSize = Stage::GetCurrent().GetSize();
585 switch(mPositionType)
587 case Toolkit::Tooltip::Position::HOVER_POINT:
589 position = mHoverPoint + mHoverPointOffset;
590 position.y += tailHeight;
594 case Toolkit::Tooltip::Position::ABOVE:
596 Toolkit::Control control = mControl.GetHandle();
599 Vector3 worldPos = control.GetCurrentProperty<Vector3>(Actor::Property::WORLD_POSITION);
600 float height = control.GetRelayoutSize(Dimension::HEIGHT);
602 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
603 position.y = stageSize.height * 0.5f + worldPos.y - height * 0.5f - popupHeight * 1.0f - tailHeight;
608 case Toolkit::Tooltip::Position::BELOW:
610 Toolkit::Control control = mControl.GetHandle();
613 Vector3 worldPos = control.GetCurrentProperty<Vector3>(Actor::Property::WORLD_POSITION);
614 float height = control.GetRelayoutSize(Dimension::HEIGHT);
616 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
617 position.y = stageSize.height * 0.5f + worldPos.y + height * 0.5f + tailHeight;
623 // Ensure the Popup is still on the screen
625 if(position.x < 0.0f)
629 else if((position.x + popupWidth) > stageSize.width)
631 position.x -= position.x + popupWidth - stageSize.width;
634 bool yPosChanged = false;
635 if(position.y < 0.0f)
640 else if((position.y + popupHeight) > stageSize.height)
643 position.y -= position.y + popupHeight - stageSize.height;
646 if(yPosChanged && tail)
648 // If we change the y position, then the tail may be shown pointing to the wrong control so just hide it.
649 tail.SetProperty(Actor::Property::VISIBLE, false);
652 mPopup.SetProperty(Actor::Property::POSITION, position);
656 } // namespace Internal
658 } // namespace Toolkit