Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
MenuItem.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 Fluid.Private as P
8import "../core/UiMetrics.js" as UiMetrics
9import QtQuick
10import QtQuick.Templates as T
11
22T.MenuItem {
23 id: menuItem
24 objectName: "menuItem"
25 readonly property int _menuContentType: 0
26
28 enum Variant {
30 Vertical,
32 Baseline
33 }
34
36 enum ColorStyle {
38 Standard,
40 Vibrant
41 }
42
44 enum GroupPosition {
46 Only,
48 First,
50 Middle,
52 Last
53 }
54
56 property int variant: MenuItem.Vertical
58 property int colorStyle: MenuItem.Standard
60 property int groupPosition: MenuItem.Only
62 property string supportingText: ""
64 property string trailingText: ""
66 property var badgeContent: ""
67
69 readonly property bool _hasNamedIcon: icon.name.length > 0
71 readonly property bool _hasSourceIcon: icon.source.toString().length > 0
73 readonly property bool _hasIcon: _hasNamedIcon || _hasSourceIcon
75 readonly property string _shortcutText: action !== null
76 ? P.ShortcutUtils.text(action.shortcut) : ""
78 readonly property bool _hasShortcut: _shortcutText.length > 0
80 readonly property bool _hasBadge: badgeContent !== undefined && badgeContent !== null
81 && badgeContent.toString().length > 0
83 readonly property bool _vertical: variant === MenuItem.Vertical
85 readonly property real _iconSize: _vertical ? MD.Tokens.menu.verticalIconSize
86 : MD.Tokens.menu.iconSize
88 readonly property var _containerShape: {
89 if (!_vertical)
90 return MD.Tokens.shape.cornerNone;
91 if (checked)
92 return MD.Tokens.menu.verticalSelectedItemShape;
93 switch (groupPosition) {
94 case MenuItem.First: return MD.Tokens.menu.verticalFirstItemShape;
95 case MenuItem.Middle: return MD.Tokens.menu.verticalMiddleItemShape;
96 case MenuItem.Last: return MD.Tokens.menu.verticalLastItemShape;
97 default: return MD.Tokens.menu.verticalOnlyItemShape;
98 }
99 }
101 readonly property color _contentColor: {
102 if (!_vertical)
103 return checked ? menuItem.MD.Style.onSecondaryContainerColor
104 : menuItem.MD.Style.onSurfaceColor;
105 if (colorStyle === MenuItem.Vibrant)
106 return checked ? menuItem.MD.Style.onTertiaryColor
107 : menuItem.MD.Style.onTertiaryContainerColor;
108 return checked ? menuItem.MD.Style.onTertiaryContainerColor
109 : menuItem.MD.Style.onSurfaceColor;
110 }
112 readonly property color _secondaryColor: {
113 if (checked)
114 return _contentColor;
115 if (_vertical && colorStyle === MenuItem.Vibrant)
116 return menuItem.MD.Style.onTertiaryContainerColor;
117 return menuItem.MD.Style.onSurfaceVariantColor;
118 }
120 readonly property real _contentOpacity: enabled ? 1 : MD.Tokens.menu.disabledContentOpacity
121
122 implicitWidth: Math.max(MD.Tokens.menu.minimumWidth,
123 implicitContentWidth + leftPadding + rightPadding)
124 implicitHeight: supportingText.length > 0
125 ? Math.max(_vertical ? MD.Tokens.menu.verticalItemHeight
126 : MD.Tokens.menu.itemHeight,
127 implicitContentHeight + topPadding + bottomPadding)
128 : _vertical ? MD.Tokens.menu.verticalItemHeight : MD.Tokens.menu.itemHeight
129 leftPadding: _vertical ? MD.Tokens.menu.verticalItemLeadingSpace
130 : MD.Tokens.menu.itemHorizontalPadding
131 rightPadding: _vertical ? MD.Tokens.menu.verticalItemTrailingSpace
132 : MD.Tokens.menu.itemHorizontalPadding
133 topPadding: _vertical ? MD.Tokens.menu.verticalItemTopPadding : 0
134 bottomPadding: _vertical ? MD.Tokens.menu.verticalItemBottomPadding : 0
135 spacing: _vertical ? MD.Tokens.menu.verticalItemBetweenSpace : MD.Tokens.menu.iconLabelGap
136 hoverEnabled: true
137 focusPolicy: Qt.StrongFocus
138 LayoutMirroring.childrenInherit: true
139 Accessible.description: supportingText
140
141 contentItem: Item {
142 id: menuContent
143 readonly property int auxiliaryCount: (iconSlot.visible ? 1 : 0)
144 + (trailingLabel.visible ? 1 : 0)
145 + (badgeLabel.visible ? 1 : 0)
146 + (shortcutLabel.visible ? 1 : 0)
147 + (submenuArrow.visible ? 1 : 0)
148 readonly property real auxiliaryWidth: (iconSlot.visible ? iconSlot.width : 0)
149 + (trailingLabel.visible ? trailingLabel.implicitWidth : 0)
150 + (badgeLabel.visible ? badgeLabel.implicitWidth : 0)
151 + (shortcutLabel.visible ? shortcutLabel.implicitWidth : 0)
152 + (submenuArrow.visible ? submenuArrow.width : 0)
153 implicitWidth: auxiliaryWidth
154 + Math.max(primaryLabel.implicitWidth, supportingLabel.implicitWidth)
155 + auxiliaryCount * menuItem.spacing
156 implicitHeight: Math.max(menuItem._iconSize, labelColumn.implicitHeight)
157
158 Row {
159 anchors.fill: parent
160 spacing: menuItem.spacing
161 LayoutMirroring.enabled: menuItem.mirrored
162 LayoutMirroring.childrenInherit: true
163
164 Item {
165 id: iconSlot
166 objectName: "menuItemIcon"
167 anchors.verticalCenter: parent.verticalCenter
168 width: menuItem._iconSize
169 height: menuItem._iconSize
170 visible: menuItem._hasIcon || menuItem.checkable
171
172 MD.Symbol {
173 objectName: "menuItemSelection"
174 anchors.centerIn: parent
175 name: MD.SymbolNames.symbolCheck
176 iconWidth: menuItem._iconSize
177 iconHeight: menuItem._iconSize
178 color: menuItem._secondaryColor
179 opacity: menuItem._contentOpacity
180 visible: menuItem.checked
181 }
182 Image {
183 anchors.fill: parent
184 source: menuItem.icon.source
185 sourceSize: Qt.size(width, height)
186 fillMode: Image.PreserveAspectFit
187 opacity: menuItem._contentOpacity
188 visible: menuItem._hasSourceIcon && !menuItem.checked
189 }
190 MD.Symbol {
191 anchors.centerIn: parent
192 name: menuItem.icon.name
193 iconWidth: menuItem._iconSize
194 iconHeight: menuItem._iconSize
195 color: menuItem._secondaryColor
196 opacity: menuItem._contentOpacity
197 visible: !menuItem._hasSourceIcon && menuItem._hasNamedIcon && !menuItem.checked
198 }
199 }
200
201 Column {
202 id: labelColumn
203 width: Math.max(0, menuContent.width - menuContent.auxiliaryWidth
204 - menuContent.auxiliaryCount * menuItem.spacing)
205 anchors.verticalCenter: parent.verticalCenter
206 spacing: 0
207 MD.Label {
208 id: primaryLabel
209 objectName: "menuItemLabel"
210 width: parent.width
211 text: menuItem.text
212 typescale: MD.Tokens.typescale.labelLarge
213 color: menuItem._contentColor
214 opacity: menuItem._contentOpacity
215 horizontalAlignment: menuItem.mirrored ? Text.AlignRight : Text.AlignLeft
216 wrapMode: Text.NoWrap
217 elide: Text.ElideRight
218 }
219 MD.Label {
220 id: supportingLabel
221 objectName: "menuItemSupportingText"
222 width: parent.width
223 text: menuItem.supportingText
224 typescale: MD.Tokens.typescale.bodySmall
225 color: menuItem._secondaryColor
226 opacity: menuItem._contentOpacity
227 horizontalAlignment: menuItem.mirrored ? Text.AlignRight : Text.AlignLeft
228 wrapMode: Text.NoWrap
229 elide: Text.ElideRight
230 visible: text.length > 0
231 }
232 }
233
234 MD.Label {
235 id: trailingLabel
236 objectName: "menuItemTrailingText"
237 anchors.verticalCenter: parent.verticalCenter
238 text: menuItem.trailingText
239 typescale: MD.Tokens.typescale.labelLarge
240 color: menuItem._secondaryColor
241 opacity: menuItem._contentOpacity
242 visible: text.length > 0
243 }
244 MD.Label {
245 id: badgeLabel
246 objectName: "menuItemBadge"
247 anchors.verticalCenter: parent.verticalCenter
248 text: menuItem._hasBadge ? menuItem.badgeContent.toString() : ""
249 typescale: MD.Tokens.typescale.labelSmall
250 color: menuItem._secondaryColor
251 opacity: menuItem._contentOpacity
252 visible: menuItem._hasBadge
253 }
254 MD.Label {
255 id: shortcutLabel
256 objectName: "menuItemShortcut"
257 anchors.verticalCenter: parent.verticalCenter
258 text: menuItem._shortcutText
259 typescale: MD.Tokens.typescale.labelLarge
260 color: menuItem._secondaryColor
261 opacity: menuItem._contentOpacity
262 visible: menuItem._hasShortcut
263 }
264 MD.Symbol {
265 id: submenuArrow
266 objectName: "menuItemSubmenuArrow"
267 anchors.verticalCenter: parent.verticalCenter
268 name: MD.SymbolNames.symbolChevronRight
269 iconWidth: menuItem._iconSize
270 iconHeight: menuItem._iconSize
271 color: menuItem._secondaryColor
272 opacity: menuItem._contentOpacity
273 visible: menuItem.subMenu !== null
274 transform: Scale {
275 origin.x: submenuArrow.width / 2
276 xScale: menuItem.mirrored ? -1 : 1
277 }
278 }
279 }
280 }
281
282 background: Rectangle {
283 objectName: "menuItemBackground"
284 color: {
285 if (!menuItem.checked)
286 return "transparent";
287 if (!menuItem._vertical)
288 return menuItem.MD.Style.secondaryContainerColor;
289 return menuItem.colorStyle === MenuItem.Vibrant
290 ? menuItem.MD.Style.tertiaryColor
291 : menuItem.MD.Style.tertiaryContainerColor;
292 }
293 topLeftRadius: UiMetrics.resolveShapeRadius(menuItem._containerShape.topLeft, width, height)
294 topRightRadius: UiMetrics.resolveShapeRadius(menuItem._containerShape.topRight, width, height)
295 bottomLeftRadius: UiMetrics.resolveShapeRadius(menuItem._containerShape.bottomLeft, width, height)
296 bottomRightRadius: UiMetrics.resolveShapeRadius(menuItem._containerShape.bottomRight, width, height)
297
298 MD.Ripple {
299 objectName: "menuItemStateLayer"
300 anchors.fill: parent
301 pressed: menuItem.pressed
302 pressX: menuItem.pressX
303 pressY: menuItem.pressY
304 color: menuItem._contentColor
305 topLeftRadius: parent.topLeftRadius
306 topRightRadius: parent.topRightRadius
307 bottomLeftRadius: parent.bottomLeftRadius
308 bottomRightRadius: parent.bottomRightRadius
309 stateOpacity: {
310 if (!menuItem.enabled)
311 return 0;
312 if (menuItem.pressed)
313 return MD.Tokens.menu.pressedStateLayerOpacity;
314 if (menuItem.visualFocus || menuItem.highlighted)
315 return MD.Tokens.menu.focusStateLayerOpacity;
316 if (menuItem.hovered)
317 return MD.Tokens.menu.hoverStateLayerOpacity;
318 return 0;
319 }
320 }
321
322 }
323}
Material Design 3 design tokens.
Definition tokens.h:65
Displays text using a selectable Material 3 type scale.
Definition Label.qml:15
A selectable Material Design 3 menu item.
Definition MenuItem.qml:17
Material Symbols icon tokens.
Definition symbol.h:17