Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
RichToolTip.qml
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-License-Identifier: MPL-2.0
3
4pragma ComponentBehavior: Bound
5
6import Fluid as MD
7import "../core/UiMetrics.js" as UiMetrics
8import "../internal/MotionAnimation.js" as MotionAnimation
9
10import QtQuick
11import QtQuick.Templates as T
12
44T.ToolTip {
45 id: control
46 objectName: "richToolTip"
47
48 MD.Style.theme: control.parent ? control.parent.MD.Style.theme : MD.Style.System
49
51 property string headline: ""
52
54 property alias body: control.text
55
63 property list<MD.Action> actions
64
66 readonly property int _displayedActionCount: Math.min(actions.length, 2)
68 readonly property bool _hasStructuredContent: headline.length > 0
69 || _displayedActionCount > 0
71 readonly property real _anchorWindowY: parent
72 ? parent.mapToItem(null, 0, 0).y + parent.y * 0 : 0
74 readonly property bool _opensBelow: parent
75 ? _anchorWindowY - height
76 - MD.Tokens.toolTip.anchorSpacing
77 < MD.Tokens.toolTip.viewportMargin : false
79 readonly property bool _layoutMirrored: mirrored
80 || locale.textDirection === Qt.RightToLeft
81 || (parent && parent.LayoutMirroring.enabled)
83 readonly property real _actionNaturalWidth: (_displayedActionCount > 0
84 ? firstActionButton.implicitWidth : 0)
85 + (_displayedActionCount > 1
86 ? secondActionButton.implicitWidth : 0)
88 readonly property real _naturalContentWidth: Math.max(headlineLabel.implicitWidth,
89 bodyLabel.implicitWidth,
90 _actionNaturalWidth)
92 property Item _previousFocusItem: null
93
94 function _rememberFocus() {
95 const anchorWindow = control.parent ? control.parent.Window.window : null;
96 control._previousFocusItem = anchorWindow ? anchorWindow.activeFocusItem : null;
97 }
98
99 function _focusFirstAction() {
100 if (firstActionButton.visible && firstActionButton.enabled) {
101 firstActionButton.forceActiveFocus(Qt.PopupFocusReason);
102 } else if (secondActionButton.visible && secondActionButton.enabled) {
103 secondActionButton.forceActiveFocus(Qt.PopupFocusReason);
104 }
105 }
106
107 function _restoreFocus() {
108 if (control._previousFocusItem)
109 control._previousFocusItem.forceActiveFocus(Qt.PopupFocusReason);
110 control._previousFocusItem = null;
111 }
112
113 onActionsChanged: {
114 if (actions.length > 2)
115 console.warn("RichToolTip supports at most two actions; additional actions are ignored");
116 }
117 onAboutToShow: control._rememberFocus()
118 onOpened: Qt.callLater(control._focusFirstAction)
119 onClosed: control._restoreFocus()
120
121 x: parent ? (_layoutMirrored ? parent.width - width : 0) : 0
122 y: parent ? (_opensBelow
123 ? parent.height + MD.Tokens.toolTip.anchorSpacing
124 : -height - MD.Tokens.toolTip.anchorSpacing) : 0
125
126 implicitWidth: Math.max(MD.Tokens.toolTip.minimumWidth,
127 Math.min(MD.Tokens.toolTip.richMaximumWidth,
128 _naturalContentWidth + leftPadding + rightPadding))
129 implicitHeight: Math.max(MD.Tokens.toolTip.minimumHeight, implicitContentHeight)
130
131 margins: MD.Tokens.toolTip.viewportMargin
132 leftPadding: MD.Tokens.toolTip.richHorizontalPadding
133 rightPadding: MD.Tokens.toolTip.richHorizontalPadding
134 topPadding: 0
135 bottomPadding: 0
136 focus: _displayedActionCount > 0
137 closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutside
138 transformOrigin: {
139 if (_opensBelow)
140 return _layoutMirrored ? Item.TopRight : Item.TopLeft;
141 return _layoutMirrored ? Item.BottomRight : Item.BottomLeft;
142 }
143
144 contentItem: Column {
145 id: contentColumn
146 objectName: "richToolTipContent"
147 width: control.availableWidth
148 Accessible.role: Accessible.ToolTip
149 Accessible.name: control.headline.length > 0 ? control.headline : control.text
150 Accessible.description: control.headline.length > 0 ? control.text : ""
151
152 Item {
153 id: headlineSlot
154 objectName: "richToolTipHeadlineSlot"
155 width: parent.width
156 height: control.headline.length > 0
157 ? MD.Tokens.toolTip.richHeadlineFirstBaseline
158 + Math.max(0, headlineLabel.implicitHeight
159 - headlineLabel.baselineOffset) : 0
160 visible: control.headline.length > 0
161
162 MD.Label {
163 id: headlineLabel
164 objectName: "richToolTipHeadline"
165 y: MD.Tokens.toolTip.richHeadlineFirstBaseline - baselineOffset
166 width: parent.width
167 text: control.headline
168 typescale: MD.Tokens.typescale.titleSmall
169 color: control.MD.Style.onSurfaceVariantColor
170 horizontalAlignment: control._layoutMirrored ? Text.AlignRight : Text.AlignLeft
171 wrapMode: Text.Wrap
172 elide: Text.ElideNone
173 Accessible.ignored: true
174 }
175 }
176
177 Item {
178 id: bodySlot
179 objectName: "richToolTipBodySlot"
180 width: parent.width
181 readonly property real labelY: control._hasStructuredContent
182 ? MD.Tokens.toolTip.richBodyFirstBaseline
183 - bodyLabel.baselineOffset
184 : MD.Tokens.toolTip.plainVerticalPadding
185 height: labelY + bodyLabel.implicitHeight
186 + (control._hasStructuredContent
187 ? MD.Tokens.toolTip.richBodyBottomPadding
188 : MD.Tokens.toolTip.plainVerticalPadding)
189
190 MD.Label {
191 id: bodyLabel
192 objectName: "richToolTipBody"
193 y: bodySlot.labelY
194 width: parent.width
195 text: control.text
196 typescale: MD.Tokens.typescale.bodyMedium
197 color: control.MD.Style.onSurfaceVariantColor
198 horizontalAlignment: control._layoutMirrored ? Text.AlignRight : Text.AlignLeft
199 wrapMode: Text.Wrap
200 elide: Text.ElideNone
201 Accessible.ignored: true
202 }
203 }
204
205 Item {
206 id: actionSlot
207 objectName: "richToolTipActionSlot"
208 width: parent.width
209 readonly property bool actionsWrap: control._displayedActionCount > 1
210 && control._actionNaturalWidth > width
211 readonly property real actionContentHeight: actionsWrap
212 ? firstActionButton.implicitHeight
213 + secondActionButton.implicitHeight
214 : Math.max(firstActionButton.implicitHeight,
215 secondActionButton.implicitHeight)
216 height: control._displayedActionCount > 0
217 ? Math.max(MD.Tokens.toolTip.richActionMinimumHeight, actionContentHeight)
218 + MD.Tokens.toolTip.richActionBottomPadding : 0
219 visible: control._displayedActionCount > 0
220
221 Flow {
222 id: actionFlow
223 objectName: "richToolTipActionFlow"
224 width: parent.width
225 height: actionSlot.height - MD.Tokens.toolTip.richActionBottomPadding
226 spacing: 0
227 layoutDirection: control._layoutMirrored ? Qt.RightToLeft : Qt.LeftToRight
228
229 MD.Button {
230 id: firstActionButton
231 objectName: "richToolTipAction0"
232 width: Math.min(implicitWidth, actionFlow.width)
233 action: control.actions.length > 0 ? control.actions[0] : null
234 type: MD.Button.Text
235 size: MD.Button.ExtraSmall
236 display: MD.Button.TextOnly
237 typescale: MD.Tokens.typescale.labelLarge
238 visible: control._displayedActionCount > 0
239 KeyNavigation.tab: secondActionButton.visible ? secondActionButton : firstActionButton
240 KeyNavigation.backtab: secondActionButton.visible ? secondActionButton : firstActionButton
241 Accessible.role: Accessible.Button
242 Accessible.name: action ? action.text : text
243
244 onClicked: control.close()
245 }
246
247 MD.Button {
248 id: secondActionButton
249 objectName: "richToolTipAction1"
250 width: Math.min(implicitWidth, actionFlow.width)
251 action: control.actions.length > 1 ? control.actions[1] : null
252 type: MD.Button.Text
253 size: MD.Button.ExtraSmall
254 display: MD.Button.TextOnly
255 typescale: MD.Tokens.typescale.labelLarge
256 visible: control._displayedActionCount > 1
257 KeyNavigation.tab: firstActionButton
258 KeyNavigation.backtab: firstActionButton.visible ? firstActionButton : null
259 Accessible.role: Accessible.Button
260 Accessible.name: action ? action.text : text
261
262 onClicked: control.close()
263 }
264 }
265 }
266 }
267
268 background: MD.ElevationRectangle {
269 objectName: "richToolTipBackground"
270 readonly property var containerShape: MD.Tokens.toolTip.richContainerShape
271
272 implicitWidth: MD.Tokens.toolTip.minimumWidth
273 implicitHeight: MD.Tokens.toolTip.minimumHeight
274 color: control.MD.Style.surfaceContainerColor
275 topLeftRadius: UiMetrics.resolveShapeRadius(containerShape.topLeft, width, height)
276 topRightRadius: UiMetrics.resolveShapeRadius(containerShape.topRight, width, height)
277 bottomLeftRadius: UiMetrics.resolveShapeRadius(containerShape.bottomLeft, width, height)
278 bottomRightRadius: UiMetrics.resolveShapeRadius(containerShape.bottomRight, width, height)
279 elevation: MD.Tokens.toolTip.richContainerElevation
280 }
281
282 enter: Transition {
283 NumberAnimation {
284 property: "scale"
285 from: MD.Tokens.toolTip.closedScale
286 to: 1
287 duration: MotionAnimation.expressiveFastSpatialDuration
288 easing.type: Easing.BezierSpline
289 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
290 }
291 NumberAnimation {
292 property: "opacity"
293 from: 0
294 to: 1
295 duration: MotionAnimation.expressiveFastEffectsDuration
296 easing.type: Easing.BezierSpline
297 easing.bezierCurve: MotionAnimation.expressiveFastEffectsCurve
298 }
299 }
300
301 exit: Transition {
302 NumberAnimation {
303 property: "scale"
304 from: 1
305 to: MD.Tokens.toolTip.closedScale
306 duration: MotionAnimation.expressiveFastSpatialDuration
307 easing.type: Easing.BezierSpline
308 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
309 }
310 NumberAnimation {
311 property: "opacity"
312 from: 1
313 to: 0
314 duration: MotionAnimation.expressiveFastEffectsDuration
315 easing.type: Easing.BezierSpline
316 easing.bezierCurve: MotionAnimation.expressiveFastEffectsCurve
317 }
318 }
319}
A rectangle that casts a Material 3 elevation shadow.
Material Design 3 design tokens.
Definition tokens.h:65
Tokens(QObject *parent=nullptr)
Definition tokens.cpp:8
Material Design 3 Expressive button tokens.
Definition button.h:21