Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
FabMenuItem.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 QtQuick
7import QtQuick.Templates as T
8import Fluid as MD
9import "../core/UiMetrics.js" as UiMetrics
10import "../internal/MotionAnimation.js" as MotionAnimation
11
38T.AbstractButton {
39 id: control
40
42 property MD.FabMenu menu: null
43
50 property int staggerIndex: 0
51
53 property color containerColor: control.menu ? control.menu.itemContainerColor : control.MD.Style.primaryContainerColor
54
56 property color contentColor: control.menu ? control.menu.itemContentColor : control.MD.Style.onPrimaryContainerColor
57
59 readonly property color effectiveContainerColor: control.containerColor
60
62 readonly property color effectiveContentColor: control.contentColor
63
65 readonly property bool _hasNamedIcon: icon.name.length > 0
66
68 readonly property bool _hasSourceIcon: icon.source.toString().length > 0
69
71 readonly property bool _hasIcon: _hasNamedIcon || _hasSourceIcon
72
74 readonly property real _contentOpacity: enabled ? 1 : MD.Tokens.fabMenu.disabledContentOpacity
75
77 readonly property bool _animated: menu !== null
78
88 readonly property int _entranceOrder: control._animated && control.menu._entranceDirection > 0 ? Math.max(0, control.menu.contentChildren.length - 1 - control.staggerIndex) : control.staggerIndex
89
91 readonly property real _collapsedScale: 0.8
92
94 readonly property real _collapsedOffsetFactor: 0.5
95
97 readonly property real _collapsedOffset: _animated ? control.height * control._collapsedOffsetFactor * control.menu._entranceDirection : 0
98
100 property real _entranceOffset: 0
101
102 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
103 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding)
104
105 leftInset: 0
106 rightInset: 0
107 topInset: 0
108 bottomInset: 0
109 leftPadding: mirrored ? MD.Tokens.fabMenu.listItemTrailingSpace : MD.Tokens.fabMenu.listItemLeadingSpace
110 rightPadding: mirrored ? MD.Tokens.fabMenu.listItemLeadingSpace : MD.Tokens.fabMenu.listItemTrailingSpace
111 topPadding: 0
112 bottomPadding: 0
113 spacing: MD.Tokens.fabMenu.listItemIconLabelSpace
114
115 hoverEnabled: true
116 focusPolicy: menu ? Qt.ClickFocus : Qt.StrongFocus
117 LayoutMirroring.childrenInherit: true
118
119 transform: Translate {
120 y: control._entranceOffset
121 }
122
123 states: [
124 State {
125 name: "expanded"
126 when: control._animated && control.menu.expanded
127 },
128 State {
129 name: "collapsed"
130 when: control._animated && !control.menu.expanded
131
132 PropertyChanges {
133 control.opacity: 0
134 control.scale: control._collapsedScale
135 control._entranceOffset: control._collapsedOffset
136 control.visible: false
137 }
138 }
139 ]
140
141 transitions: [
142 Transition {
143 from: "expanded"
144 to: "collapsed"
145
146 SequentialAnimation {
147 PauseAnimation {
148 duration: control._entranceOrder * MD.Tokens.fabMenu.listItemStaggerDelay
149 }
150 ParallelAnimation {
151 NumberAnimation {
152 target: control
153 properties: "scale,_entranceOffset"
154 // qmllint disable unresolved-type
155 easing.type: Easing.BezierSpline
156 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
157 duration: MotionAnimation.expressiveFastSpatialDuration
158 // qmllint enable unresolved-type
159 }
160 NumberAnimation {
161 target: control
162 property: "opacity"
163 // qmllint disable unresolved-type
164 easing.type: Easing.BezierSpline
165 easing.bezierCurve: MotionAnimation.expressiveFastEffectsCurve
166 duration: MotionAnimation.expressiveFastEffectsDuration
167 // qmllint enable unresolved-type
168 }
169 }
170 PropertyAction {
171 target: control
172 property: "visible"
173 }
174 }
175 },
176 Transition {
177 from: "collapsed"
178 to: "expanded"
179
180 SequentialAnimation {
181 PropertyAction {
182 target: control
183 property: "visible"
184 }
185 PauseAnimation {
186 duration: control._entranceOrder * MD.Tokens.fabMenu.listItemStaggerDelay
187 }
188 ParallelAnimation {
189 NumberAnimation {
190 target: control
191 properties: "scale,_entranceOffset"
192 // qmllint disable unresolved-type
193 easing.type: Easing.BezierSpline
194 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
195 duration: MotionAnimation.expressiveFastSpatialDuration
196 // qmllint enable unresolved-type
197 }
198 NumberAnimation {
199 target: control
200 property: "opacity"
201 // qmllint disable unresolved-type
202 easing.type: Easing.BezierSpline
203 easing.bezierCurve: MotionAnimation.expressiveFastEffectsCurve
204 duration: MotionAnimation.expressiveFastEffectsDuration
205 // qmllint enable unresolved-type
206 }
207 }
208 }
209 }
210 ]
211
212 contentItem: Item {
213 id: itemContent
214
215 readonly property real leadingWidth: iconSlot.visible ? iconSlot.width + control.spacing : 0
216
217 implicitWidth: leadingWidth + label.implicitWidth
218 implicitHeight: Math.max(MD.Tokens.fabMenu.listItemIconSize, label.implicitHeight)
219
220 Row {
221 anchors.fill: parent
222 spacing: control.spacing
223 LayoutMirroring.enabled: control.mirrored
224 LayoutMirroring.childrenInherit: true
225
226 Item {
227 id: iconSlot
228 objectName: "fabMenuItemIcon"
229
230 anchors.verticalCenter: parent.verticalCenter
231 width: MD.Tokens.fabMenu.listItemIconSize
232 height: MD.Tokens.fabMenu.listItemIconSize
233 visible: control._hasIcon
234
235 Image {
236 anchors.fill: parent
237 source: control.icon.source
238 sourceSize: Qt.size(width, height)
239 fillMode: Image.PreserveAspectFit
240 opacity: control._contentOpacity
241 visible: control._hasSourceIcon
242 }
243
244 MD.Symbol {
245 anchors.centerIn: parent
246 name: control.icon.name
247 iconWidth: MD.Tokens.fabMenu.listItemIconSize
248 iconHeight: MD.Tokens.fabMenu.listItemIconSize
249 color: control.effectiveContentColor
250 opacity: control._contentOpacity
251 visible: !control._hasSourceIcon && control._hasNamedIcon
252 }
253 }
254
255 MD.Label {
256 id: label
257 objectName: "fabMenuItemLabel"
258
259 width: Math.max(0, itemContent.width - itemContent.leadingWidth)
260 anchors.verticalCenter: parent.verticalCenter
261 text: control.text
262 // qmllint disable unresolved-type
263 typescale: MD.Tokens.typescale.titleMedium
264 // qmllint enable unresolved-type
265 color: control.effectiveContentColor
266 opacity: control._contentOpacity
267 horizontalAlignment: control.mirrored ? Text.AlignRight : Text.AlignLeft
268 elide: Text.ElideRight
269 }
270 }
271 }
272
273 background: MD.ElevationRectangle {
274 implicitHeight: MD.Tokens.fabMenu.listItemContainerHeight
275 topLeftRadius: UiMetrics.resolveShapeRadius(
276 MD.Tokens.fabMenu.listItemContainerShape.topLeft, width, height)
277 topRightRadius: UiMetrics.resolveShapeRadius(
278 MD.Tokens.fabMenu.listItemContainerShape.topRight, width, height)
279 bottomLeftRadius: UiMetrics.resolveShapeRadius(
280 MD.Tokens.fabMenu.listItemContainerShape.bottomLeft, width, height)
281 bottomRightRadius: UiMetrics.resolveShapeRadius(
282 MD.Tokens.fabMenu.listItemContainerShape.bottomRight, width,
283 height)
284 color: control.effectiveContainerColor
285 elevation: MD.Tokens.fabMenu.listItemContainerElevation
286
287 MD.Ripple {
288 objectName: "fabMenuItemRipple"
289
290 anchors.fill: parent
291 topLeftRadius: parent.topLeftRadius
292 topRightRadius: parent.topRightRadius
293 bottomLeftRadius: parent.bottomLeftRadius
294 bottomRightRadius: parent.bottomRightRadius
295 pressed: control.pressed
296 pressX: control.pressX
297 pressY: control.pressY
298 color: control.effectiveContentColor
299 stateOpacity: {
300 if (!control.enabled)
301 return 0;
302 if (control.pressed)
303 return MD.Tokens.fabMenu.pressedStateLayerOpacity;
304 if (control.visualFocus)
305 return MD.Tokens.fabMenu.focusStateLayerOpacity;
306 if (control.hovered)
307 return MD.Tokens.fabMenu.hoverStateLayerOpacity;
308 return 0;
309 }
310 }
311 }
312}