Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
IconButton.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 "../internal" as Internal
10import "../core/BadgeContent.js" as BadgeContent
11import "../core/UiMetrics.js" as UiMetrics
12import "../internal/MotionAnimation.js" as MotionAnimation
13
27T.ToolButton {
28 id: control
29
31 property var badgeContent: _badgeAction ? _badgeAction.badgeContent : ""
32
34 property bool badgeNotification: _badgeAction ? _badgeAction.badgeNotification : false
35
37 property bool badgeVisible: _badgeAction ? _badgeAction.badgeVisible : true
38
40 property string badgeAccessibleDescription: _badgeAction ? _badgeAction.badgeAccessibleDescription : ""
41
43 property int badgeVariant: MD.Badge.Auto
44
45 readonly property MD.Action _badgeAction: action as MD.Action
46 readonly property bool _showBadge: badgeVisible
47 && (badgeNotification || BadgeContent.hasContent(badgeContent))
48
49 component IconData: QtObject {
50 property string name
51 property url source
52 property color color
53 property bool fill: false
54 }
55
57 enum Type {
58 Filled,
59 Tonal,
60 Outlined,
61 Standard
62 }
63
68 enum Shape {
69 Round,
70 Square
71 }
72
74 enum Size {
75 ExtraSmall,
76 Small,
77 Medium,
78 Large,
79 ExtraLarge
80 }
81
83 enum Width {
84 Default,
85 Narrow,
86 Wide
87 }
88
94 property int type: MD.IconButton.Type.Filled
95
101 property int shape: MD.IconButton.Shape.Round
102
109 property int size: MD.IconButton.Size.Small
110
116 property int widthVariant: MD.IconButton.Width.Default
117
118 // Set by ButtonGroup while this button is one of its direct children.
119 property var __buttonGroup: null
120 property real __groupTopLeftRadius: -1
121 property real __groupTopRightRadius: -1
122 property real __groupBottomLeftRadius: -1
123 property real __groupBottomRightRadius: -1
124
131 property IconData checkedIcon: IconData {}
132
134 property bool mirrorIconInRtl: false
135
140 property bool tintSourceIcon: true
141
146 property color contentColor: {
147 switch (type) {
148 case MD.IconButton.Type.Filled:
149 return checked ? MD.Style.onPrimaryColor : MD.Style.onSurfaceVariantColor;
150 case MD.IconButton.Type.Tonal:
151 return checked ? MD.Style.onSecondaryColor : MD.Style.onSecondaryContainerColor;
152 case MD.IconButton.Type.Outlined:
153 return checked ? MD.Style.inverseOnSurfaceColor : MD.Style.onSurfaceVariantColor;
154 case MD.IconButton.Type.Standard:
155 return checked ? MD.Style.primaryColor : MD.Style.onSurfaceVariantColor;
156 }
157 }
158
163 property color containerColor: {
164 switch (type) {
165 case MD.IconButton.Type.Filled:
166 return checked ? MD.Style.primaryColor : MD.Style.surfaceContainerColor;
167 case MD.IconButton.Type.Tonal:
168 return checked ? MD.Style.secondaryColor : MD.Style.secondaryContainerColor;
169 case MD.IconButton.Type.Outlined:
170 return checked ? MD.Style.inverseSurfaceColor : "transparent";
171 case MD.IconButton.Type.Standard:
172 return "transparent";
173 }
174 }
175
177 property color disabledContentColor: MD.Style.onSurfaceColor
178
180 property color disabledContainerColor: MD.Style.onSurfaceColor
181
183 property color outlineColor: MD.Style.outlineVariantColor
184
186 readonly property color effectiveContentColor: visualState.contentColor
187
189 readonly property real effectiveContentOpacity: visualState.contentOpacity
190
192 readonly property color effectiveContainerColor: visualState.containerColor
193
195 readonly property bool usingCheckedIcon: checked && (checkedIcon.name.length > 0 || checkedIcon.source.toString().length > 0)
196
198 readonly property string effectiveIconName: usingCheckedIcon ? checkedIcon.name : icon.name
199
201 readonly property url effectiveIconSource: usingCheckedIcon ? checkedIcon.source : icon.source
202
204 readonly property color effectiveIconColor: {
205 const explicitColor = usingCheckedIcon ? checkedIcon.color : icon.color;
206 return explicitColor.a > 0 ? explicitColor : effectiveContentColor;
207 }
208
210 readonly property bool effectiveIconFill: checked && (usingCheckedIcon ? checkedIcon.fill : true)
211
213 readonly property bool effectiveIconMirrored: mirrorIconInRtl && mirrored
214
215 icon.width: visualState.iconSize.width
216 icon.height: visualState.iconSize.height
217
218 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
219 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding)
220
221 leftInset: visualState.inset.width / 2
222 rightInset: visualState.inset.width / 2
223 topInset: visualState.inset.height / 2
224 bottomInset: visualState.inset.height / 2
225
226 padding: 0
227
228 spacing: 0
229
230 hoverEnabled: true
231 focusPolicy: Qt.StrongFocus
232
233 onCheckedChanged: {
234 if (__buttonGroup)
235 __buttonGroup.__childCheckedChanged(control);
236 }
237 onActiveFocusChanged: {
238 if (activeFocus && __buttonGroup)
239 __buttonGroup.__childFocused(control);
240 }
241 onPressedChanged: {
242 if (__buttonGroup)
243 __buttonGroup.__childGeometryChanged();
244 }
245 onImplicitWidthChanged: {
246 if (__buttonGroup)
247 __buttonGroup.__childGeometryChanged();
248 }
249 onVisibleChanged: {
250 if (__buttonGroup)
251 __buttonGroup.__childrenChanged();
252 }
253 onEnabledChanged: {
254 if (__buttonGroup)
255 __buttonGroup.__childrenChanged();
256 }
257 Keys.onPressed: event => {
258 if (__buttonGroup && __buttonGroup.__handleKey(control, event))
259 event.accepted = true;
260 }
261
262 Behavior on width {
263 enabled: control.__buttonGroup && control.__buttonGroup.__geometryInitialized
264 && control.__buttonGroup.variant === control.__buttonGroup.__standardVariant
265 NumberAnimation {
266 easing.type: Easing.BezierSpline
267 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
268 duration: MotionAnimation.expressiveFastSpatialDuration
269 }
270 }
271 Behavior on x {
272 enabled: control.__buttonGroup && control.__buttonGroup.__geometryInitialized
273 && control.__buttonGroup.variant === control.__buttonGroup.__standardVariant
274 NumberAnimation {
275 easing.type: Easing.BezierSpline
276 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
277 duration: MotionAnimation.expressiveFastSpatialDuration
278 }
279 }
280
281 Accessible.role: __buttonGroup && __buttonGroup.selectionMode === __buttonGroup.__singleSelectionMode
282 ? Accessible.RadioButton
283 : (__buttonGroup && __buttonGroup.selectionMode === __buttonGroup.__multiSelectionMode
284 ? Accessible.CheckBox : Accessible.Button)
285 Accessible.checkable: checkable
286 Accessible.checked: checked
287 Accessible.name: BadgeContent.accessibleName(text,
288 iconButtonBadge && iconButtonBadge.visible ? iconButtonBadge.accessibleDescription : "")
289
290 QtObject {
291 id: visualState
292
293 readonly property real currentContainerHeight: {
294 switch (control.size) {
295 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.containerHeightExtraSmall;
296 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.containerHeightSmall;
297 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.containerHeightMedium;
298 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.containerHeightLarge;
299 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.containerHeightExtraLarge;
300 }
301 }
302 readonly property real currentIconSize: {
303 switch (control.size) {
304 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.iconSizeExtraSmall;
305 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.iconSizeSmall;
306 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.iconSizeMedium;
307 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.iconSizeLarge;
308 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.iconSizeExtraLarge;
309 }
310 }
311 readonly property real currentLeadingSpace: {
312 if (control.widthVariant === MD.IconButton.Width.Narrow) {
313 switch (control.size) {
314 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.narrowLeadingSpaceExtraSmall;
315 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.narrowLeadingSpaceSmall;
316 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.narrowLeadingSpaceMedium;
317 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.narrowLeadingSpaceLarge;
318 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.narrowLeadingSpaceExtraLarge;
319 }
320 }
321 if (control.widthVariant === MD.IconButton.Width.Wide) {
322 switch (control.size) {
323 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.wideLeadingSpaceExtraSmall;
324 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.wideLeadingSpaceSmall;
325 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.wideLeadingSpaceMedium;
326 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.wideLeadingSpaceLarge;
327 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.wideLeadingSpaceExtraLarge;
328 }
329 }
330 switch (control.size) {
331 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.defaultLeadingSpaceExtraSmall;
332 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.defaultLeadingSpaceSmall;
333 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.defaultLeadingSpaceMedium;
334 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.defaultLeadingSpaceLarge;
335 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.defaultLeadingSpaceExtraLarge;
336 }
337 }
338 readonly property real currentTrailingSpace: {
339 if (control.widthVariant === MD.IconButton.Width.Narrow) {
340 switch (control.size) {
341 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.narrowTrailingSpaceExtraSmall;
342 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.narrowTrailingSpaceSmall;
343 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.narrowTrailingSpaceMedium;
344 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.narrowTrailingSpaceLarge;
345 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.narrowTrailingSpaceExtraLarge;
346 }
347 }
348 if (control.widthVariant === MD.IconButton.Width.Wide) {
349 switch (control.size) {
350 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.wideTrailingSpaceExtraSmall;
351 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.wideTrailingSpaceSmall;
352 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.wideTrailingSpaceMedium;
353 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.wideTrailingSpaceLarge;
354 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.wideTrailingSpaceExtraLarge;
355 }
356 }
357 switch (control.size) {
358 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.defaultTrailingSpaceExtraSmall;
359 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.defaultTrailingSpaceSmall;
360 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.defaultTrailingSpaceMedium;
361 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.defaultTrailingSpaceLarge;
362 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.defaultTrailingSpaceExtraLarge;
363 }
364 }
365 readonly property real outlineWidth: {
366 switch (control.size) {
367 case MD.IconButton.Size.ExtraSmall: return MD.Tokens.iconButton.outlinedOutlineWidthExtraSmall;
368 case MD.IconButton.Size.Small: return MD.Tokens.iconButton.outlinedOutlineWidthSmall;
369 case MD.IconButton.Size.Medium: return MD.Tokens.iconButton.outlinedOutlineWidthMedium;
370 case MD.IconButton.Size.Large: return MD.Tokens.iconButton.outlinedOutlineWidthLarge;
371 case MD.IconButton.Size.ExtraLarge: return MD.Tokens.iconButton.outlinedOutlineWidthExtraLarge;
372 }
373 }
374 property size buttonSize: Qt.size(currentIconSize + currentLeadingSpace + currentTrailingSpace, currentContainerHeight)
375 property size iconSize: Qt.size(currentIconSize, currentIconSize)
376
377 property size inset: {
378 const minimum = MD.Tokens.iconButton.minimumInteractiveSize;
379 return Qt.size(Math.max(minimum, visualState.buttonSize.width) - visualState.buttonSize.width,
380 Math.max(minimum, visualState.buttonSize.height) - visualState.buttonSize.height);
381 }
382
383 property MD.shapeValue containerShape: {
384 const tokens = MD.Tokens.iconButton;
385 if (control.pressed) {
386 switch (control.size) {
387 case MD.IconButton.Size.ExtraSmall: return tokens.pressedContainerShapeExtraSmall;
388 case MD.IconButton.Size.Small: return tokens.pressedContainerShapeSmall;
389 case MD.IconButton.Size.Medium: return tokens.pressedContainerShapeMedium;
390 case MD.IconButton.Size.Large: return tokens.pressedContainerShapeLarge;
391 case MD.IconButton.Size.ExtraLarge: return tokens.pressedContainerShapeExtraLarge;
392 }
393 }
394 if (control.checked) {
395 if (control.shape === MD.IconButton.Shape.Square)
396 return tokens.selectedContainerShapeSquare;
397 switch (control.size) {
398 case MD.IconButton.Size.ExtraSmall: return tokens.selectedContainerShapeRoundExtraSmall;
399 case MD.IconButton.Size.Small: return tokens.selectedContainerShapeRoundSmall;
400 case MD.IconButton.Size.Medium: return tokens.selectedContainerShapeRoundMedium;
401 case MD.IconButton.Size.Large: return tokens.selectedContainerShapeRoundLarge;
402 case MD.IconButton.Size.ExtraLarge: return tokens.selectedContainerShapeRoundExtraLarge;
403 }
404 }
405 if (control.shape === MD.IconButton.Shape.Round)
406 return tokens.containerShapeRound;
407 switch (control.size) {
408 case MD.IconButton.Size.ExtraSmall: return tokens.containerShapeSquareExtraSmall;
409 case MD.IconButton.Size.Small: return tokens.containerShapeSquareSmall;
410 case MD.IconButton.Size.Medium: return tokens.containerShapeSquareMedium;
411 case MD.IconButton.Size.Large: return tokens.containerShapeSquareLarge;
412 case MD.IconButton.Size.ExtraLarge: return tokens.containerShapeSquareExtraLarge;
413 }
414 }
415
416 property color containerColor: control.containerColor
417 property color contentColor: control.contentColor
418 property color stateLayerColor: "transparent"
419
420 property real contentOpacity: 1.0
421 property real containerOpacity: 1.0
422 property real stateLayerOpacity: 0.0
423 }
424
425 states: [
426 State {
427 name: "disabled"
428 when: !control.enabled
429
430 PropertyChanges {
431 visualState.containerColor: control.type === MD.IconButton.Type.Standard ? "transparent" : control.disabledContainerColor
432 visualState.contentColor: control.disabledContentColor
433 visualState.containerOpacity: MD.Tokens.iconButton.disabledContainerOpacity
434 visualState.contentOpacity: MD.Tokens.iconButton.disabledIconOpacity
435 }
436 },
437 State {
438 name: "pressed"
439 when: control.down && control.enabled
440
441 PropertyChanges {
442 visualState.stateLayerColor: control.effectiveIconColor
443 visualState.stateLayerOpacity: MD.Tokens.iconButton.pressedStateLayerOpacity
444 }
445 },
446 State {
447 name: "focused"
448 when: control.visualFocus && control.enabled
449
450 PropertyChanges {
451 visualState.stateLayerColor: control.effectiveIconColor
452 visualState.stateLayerOpacity: MD.Tokens.iconButton.focusStateLayerOpacity
453 }
454 },
455 State {
456 name: "hovered"
457 when: control.hovered && control.enabled
458
459 PropertyChanges {
460 visualState.stateLayerColor: control.effectiveIconColor
461 visualState.stateLayerOpacity: MD.Tokens.iconButton.hoverStateLayerOpacity
462 }
463 }
464 ]
465
466 contentItem: Item {
467 id: iconContent
468
469 implicitWidth: control.icon.width
470 implicitHeight: control.icon.height
471
472 Item {
473 id: iconSlot
474 objectName: "iconButtonIconSlot"
475 anchors.centerIn: parent
476 width: control.icon.width
477 height: control.icon.height
478 visible: control.effectiveIconSource.toString().length > 0
479 || control.effectiveIconName.length > 0
480 Accessible.ignored: true
481
482 transform: Scale {
483 origin.x: iconSlot.width / 2
484 origin.y: iconSlot.height / 2
485 xScale: control.effectiveIconMirrored ? -1 : 1
486 }
487
488 Internal.IconImage {
489 id: sourceIcon
490 objectName: "iconButtonSourceImage"
491 anchors.fill: parent
492 source: control.effectiveIconSource
493 sourceSize: Qt.size(control.icon.width, control.icon.height)
494 fillMode: Image.PreserveAspectFit
495 color: control.tintSourceIcon ? control.effectiveIconColor : "transparent"
496 opacity: visualState.contentOpacity
497 visible: control.effectiveIconSource.toString().length > 0
498 Accessible.ignored: true
499 }
500
501 MD.Symbol {
502 objectName: "iconButtonSymbol"
503 anchors.fill: parent
504 name: control.effectiveIconName
505 iconWidth: control.icon.width
506 iconHeight: control.icon.height
507 color: control.effectiveIconColor
508 opacity: visualState.contentOpacity
509 fill: control.effectiveIconFill
510 visible: control.effectiveIconSource.toString().length === 0 && name.length > 0
511 Accessible.ignored: true
512 }
513 }
514
515 MD.BadgeOverlay {
516 id: iconButtonBadge
517 objectName: "iconButtonBadge"
518 target: iconSlot
519 layoutMirrored: control.mirrored
520 count: BadgeContent.count(control.badgeContent)
521 text: BadgeContent.text(control.badgeContent)
522 locale: control.locale
523 variant: control.badgeNotification ? MD.Badge.Small : control.badgeVariant
524 accessibleDescription: control.badgeAccessibleDescription.length > 0
525 ? control.badgeAccessibleDescription : _defaultAccessibleDescription
526 visible: control._showBadge && targetAvailable
527 }
528 }
529
530 background: Rectangle {
531 implicitWidth: visualState.buttonSize.width
532 implicitHeight: visualState.buttonSize.height
533
534 topLeftRadius: control.__groupTopLeftRadius >= 0 ? control.__groupTopLeftRadius : UiMetrics.resolveShapeRadius(visualState.containerShape.topLeft, width, height)
535 topRightRadius: control.__groupTopRightRadius >= 0 ? control.__groupTopRightRadius : UiMetrics.resolveShapeRadius(visualState.containerShape.topRight, width, height)
536 bottomLeftRadius: control.__groupBottomLeftRadius >= 0 ? control.__groupBottomLeftRadius : UiMetrics.resolveShapeRadius(visualState.containerShape.bottomLeft, width, height)
537 bottomRightRadius: control.__groupBottomRightRadius >= 0 ? control.__groupBottomRightRadius : UiMetrics.resolveShapeRadius(visualState.containerShape.bottomRight, width, height)
538
539 Behavior on topLeftRadius {
540 enabled: !control.__buttonGroup || control.__buttonGroup.__geometryInitialized
541 NumberAnimation {
542 easing.type: Easing.BezierSpline
543 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
544 duration: MotionAnimation.expressiveFastSpatialDuration
545 }
546 }
547 Behavior on topRightRadius {
548 enabled: !control.__buttonGroup || control.__buttonGroup.__geometryInitialized
549 NumberAnimation {
550 easing.type: Easing.BezierSpline
551 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
552 duration: MotionAnimation.expressiveFastSpatialDuration
553 }
554 }
555 Behavior on bottomLeftRadius {
556 enabled: !control.__buttonGroup || control.__buttonGroup.__geometryInitialized
557 NumberAnimation {
558 easing.type: Easing.BezierSpline
559 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
560 duration: MotionAnimation.expressiveFastSpatialDuration
561 }
562 }
563 Behavior on bottomRightRadius {
564 enabled: !control.__buttonGroup || control.__buttonGroup.__geometryInitialized
565 NumberAnimation {
566 easing.type: Easing.BezierSpline
567 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
568 duration: MotionAnimation.expressiveFastSpatialDuration
569 }
570 }
571
572 border.width: control.type === MD.IconButton.Type.Outlined && !control.checked ? visualState.outlineWidth : 0
573 border.color: control.outlineColor
574
575 opacity: visualState.containerOpacity
576 color: visualState.containerColor
577
578 MD.Ripple {
579 anchors.fill: parent
580
581 topLeftRadius: parent.topLeftRadius
582 topRightRadius: parent.topRightRadius
583 bottomLeftRadius: parent.bottomLeftRadius
584 bottomRightRadius: parent.bottomRightRadius
585
586 pressed: control.pressed
587 pressX: control.pressX
588 pressY: control.pressY
589
590 stateOpacity: visualState.stateLayerOpacity
591 color: visualState.stateLayerColor
592 }
593 }
594}
Material Design 3 Expressive icon button tokens.
Definition iconbutton.h:21