2 * Copyright (c) 2024 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, std::move(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, std::move(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 if(DALI_LIKELY(value.Get(mBackgroundImage)))
294 mBackgroundBorder.Set(0, 0, 0, 0);
297 else if(type == Property::MAP)
299 const Property::Map* map = value.GetMap();
302 const Property::Map::SizeType count = map->Count();
303 for(Property::Map::SizeType position = 0; position < count; ++position)
305 KeyValuePair keyValue = map->GetKeyValue(position);
306 Property::Key& key = keyValue.first;
307 Property::Value& value = keyValue.second;
309 if(key == Toolkit::Tooltip::Background::Property::VISUAL || key == PROPERTY_BACKGROUND_VISUAL)
311 value.Get(mBackgroundImage);
313 else if(key == Toolkit::Tooltip::Background::Property::BORDER || key == PROPERTY_BACKGROUND_BORDER)
315 if(!value.Get(mBackgroundBorder))
317 // If not a Property::RECTANGLE, then check if it's a Vector4 and set it accordingly
318 Vector4 valueVector4;
319 if(value.Get(valueVector4))
321 mBackgroundBorder.left = valueVector4.x;
322 mBackgroundBorder.right = valueVector4.y;
323 mBackgroundBorder.bottom = valueVector4.z;
324 mBackgroundBorder.top = valueVector4.w;
333 void Tooltip::SetTail(const Property::Value& value)
335 Property::Type type = value.GetType();
337 if(type == Property::BOOLEAN)
339 value.Get(mTailVisibility);
341 else if(type == Property::MAP)
346 const Property::Map::SizeType count = map.Count();
347 for(Property::Map::SizeType position = 0; position < count; ++position)
349 KeyValuePair keyValue = map.GetKeyValue(position);
350 Property::Key& key = keyValue.first;
351 Property::Value& value = keyValue.second;
353 // Set the values manually rather than merging so that we only have to deal with Property indices when creating the actual tooltip.
355 if(key == Toolkit::Tooltip::Tail::Property::VISIBILITY || key == PROPERTY_TAIL_VISIBILITY)
357 value.Get(mTailVisibility);
359 else if(key == Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL || key == PROPERTY_TAIL_ABOVE_VISUAL)
364 mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL] = path;
367 else if(key == Toolkit::Tooltip::Tail::Property::BELOW_VISUAL || key == PROPERTY_TAIL_BELOW_VISUAL)
372 mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL] = path;
380 bool Tooltip::OnHovered(Actor /* actor */, const HoverEvent& hover)
382 const PointState::Type state = hover.GetState(0);
385 case PointState::STARTED:
386 case PointState::MOTION:
392 mHoverPoint = hover.GetScreenPosition(0);
393 mTooltipTimer = Timer::New(mWaitTime);
394 mTooltipTimer.TickSignal().Connect(this, &Tooltip::OnTimeout);
395 mTooltipTimer.Start();
399 Vector2 movement = mHoverPoint - hover.GetScreenPosition(0);
400 if(std::abs(movement.Length()) > mMovementThreshold)
402 mTooltipTimer.Stop();
403 mTooltipTimer.Reset();
405 mHoverPoint = hover.GetScreenPosition(0);
406 mTooltipTimer = Timer::New(mWaitTime);
407 mTooltipTimer.TickSignal().Connect(this, &Tooltip::OnTimeout);
408 mTooltipTimer.Start();
412 else if(mDisappearOnMovement)
414 // Popup is showing, and we're set to disappear on excessive movement so make sure we're still within the threshold.
416 Vector2 movement = mHoverPoint - hover.GetScreenPosition(0);
417 if(std::abs(movement.Length()) > mMovementThreshold)
419 // Exceeding the threshold, hide the popup.
423 mTooltipTimer.Stop();
424 mTooltipTimer.Reset();
435 case PointState::FINISHED:
436 case PointState::LEAVE:
437 case PointState::INTERRUPTED:
441 mTooltipTimer.Stop();
442 mTooltipTimer.Reset();
452 case PointState::STATIONARY:
461 bool Tooltip::OnTimeout()
463 Toolkit::Control control = mControl.GetHandle();
464 if(!mPopup && control)
466 mPopup = Toolkit::Popup::New();
468 // General set up of popup
469 mPopup.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS);
470 mPopup.SetProperty(Toolkit::Popup::Property::CONTEXTUAL_MODE, "NON_CONTEXTUAL");
471 mPopup.SetProperty(Toolkit::Popup::Property::ANIMATION_MODE, "NONE");
472 mPopup.SetProperty(Toolkit::Popup::Property::BACKING_ENABLED, false); // Disable the dimmed backing.
473 mPopup.SetProperty(Toolkit::Popup::Property::TOUCH_TRANSPARENT, true); // Let events pass through the popup
474 mPopup.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
475 mPopup.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
478 mPopup.SetProperty(Toolkit::Popup::Property::POPUP_BACKGROUND_IMAGE, mBackgroundImage);
479 mPopup.SetProperty(Toolkit::Popup::Property::POPUP_BACKGROUND_BORDER, mBackgroundBorder);
482 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_VISIBILITY, mTailVisibility);
483 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_UP_IMAGE, mTailImages[Toolkit::Tooltip::Tail::Property::ABOVE_VISUAL]);
484 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_DOWN_IMAGE, mTailImages[Toolkit::Tooltip::Tail::Property::BELOW_VISUAL]);
486 Vector3 tailPosition;
487 switch(mPositionType)
489 case Toolkit::Tooltip::Position::HOVER_POINT:
490 case Toolkit::Tooltip::Position::BELOW:
492 tailPosition = Vector3(0.5f, 0.0f, 0.0);
496 case Toolkit::Tooltip::Position::ABOVE:
498 tailPosition = Vector3(0.5f, 1.0f, 0.0);
502 mPopup.SetProperty(Toolkit::Popup::Property::TAIL_POSITION, tailPosition);
506 if(!mContentTextVisual.Empty())
508 content = Toolkit::Control::New();
509 content.SetProperty(Toolkit::Control::Property::BACKGROUND, mContentTextVisual);
511 else if(!mContentArray.Empty())
513 const unsigned int visuals = mContentArray.Size();
514 unsigned int rows = mLayout.x;
515 unsigned int columns = mLayout.y;
516 if(Equals(mLayout.x, 1.0f, Math::MACHINE_EPSILON_1) &&
517 Equals(mLayout.y, 1.0f, Math::MACHINE_EPSILON_1) &&
524 Toolkit::TableView tableView = Toolkit::TableView::New(rows, columns);
525 tableView.SetResizePolicy(ResizePolicy::USE_NATURAL_SIZE, Dimension::ALL_DIMENSIONS);
527 for(unsigned int currentContent = 0, currentRow = 0; currentRow < rows && currentContent < visuals; ++currentRow)
529 tableView.SetFitHeight(currentRow);
530 for(unsigned int currentColumn = 0; currentColumn < columns && currentContent < visuals; ++currentColumn)
532 Actor child = Toolkit::Control::New();
533 child.SetProperty(Toolkit::Control::Property::BACKGROUND, mContentArray[currentContent]);
535 Toolkit::TableView::CellPosition cellPosition(currentRow, currentColumn);
536 tableView.AddChild(child, cellPosition);
537 tableView.SetCellAlignment(cellPosition, HorizontalAlignment::CENTER, VerticalAlignment::CENTER);
538 tableView.SetFitWidth(currentColumn);
545 mPopup.SetContent(content);
547 // Connect to the relayout signal of the background of the popup as at that point we have the full size
548 Actor popupBackground = GetImpl(mPopup).GetPopupBackgroundImage();
551 popupBackground.OnRelayoutSignal().Connect(this, &Tooltip::OnRelayout);
554 mPopup.SetDisplayState(Toolkit::Popup::SHOWN);
556 Stage::GetCurrent().Add(mPopup);
562 void Tooltip::OnRelayout(Actor actor)
566 float popupWidth = actor.GetRelayoutSize(Dimension::WIDTH);
567 float popupHeight = actor.GetRelayoutSize(Dimension::HEIGHT);
568 float tailHeight = 0.0f;
573 // Popup's background has the tail, we want to know the tail size as well.
574 if(actor.GetChildCount())
576 tail = actor.GetChildAt(0);
579 tailHeight = tail.GetRelayoutSize(Dimension::HEIGHT);
584 Vector2 stageSize = Stage::GetCurrent().GetSize();
587 switch(mPositionType)
589 case Toolkit::Tooltip::Position::HOVER_POINT:
591 position = mHoverPoint + mHoverPointOffset;
592 position.y += tailHeight;
596 case Toolkit::Tooltip::Position::ABOVE:
598 Toolkit::Control control = mControl.GetHandle();
601 Vector3 worldPos = control.GetCurrentProperty<Vector3>(Actor::Property::WORLD_POSITION);
602 float height = control.GetRelayoutSize(Dimension::HEIGHT);
604 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
605 position.y = stageSize.height * 0.5f + worldPos.y - height * 0.5f - popupHeight * 1.0f - tailHeight;
610 case Toolkit::Tooltip::Position::BELOW:
612 Toolkit::Control control = mControl.GetHandle();
615 Vector3 worldPos = control.GetCurrentProperty<Vector3>(Actor::Property::WORLD_POSITION);
616 float height = control.GetRelayoutSize(Dimension::HEIGHT);
618 position.x = stageSize.width * 0.5f + worldPos.x - popupWidth * 0.5f;
619 position.y = stageSize.height * 0.5f + worldPos.y + height * 0.5f + tailHeight;
625 // Ensure the Popup is still on the screen
627 if(position.x < 0.0f)
631 else if((position.x + popupWidth) > stageSize.width)
633 position.x -= position.x + popupWidth - stageSize.width;
636 bool yPosChanged = false;
637 if(position.y < 0.0f)
642 else if((position.y + popupHeight) > stageSize.height)
645 position.y -= position.y + popupHeight - stageSize.height;
648 if(yPosChanged && tail)
650 // If we change the y position, then the tail may be shown pointing to the wrong control so just hide it.
651 tail.SetProperty(Actor::Property::VISIBLE, false);
654 mPopup.SetProperty(Actor::Property::POSITION, position);
658 } // namespace Internal
660 } // namespace Toolkit