pragma ComponentBehavior: Bound import QtQuick import Weave.Controls import Weave.Templates as T import "internal" T.Tile { id: root opacity: root.enabled ? 1.0 : Theme.component.tile.opacity.disabled implicitWidth: Math.max(implicitContentWidth + leftPadding + rightPadding, implicitBackgroundWidth + leftInset + rightInset) implicitHeight: Math.max(implicitContentHeight + topPadding + bottomPadding, implicitBackgroundHeight + topInset + bottomInset) leftPadding: root.flat ? 0 : Theme.component.tile.emptyBackground.paddingLeft rightPadding: root.flat ? 0 : Theme.component.tile.emptyBackground.paddingRight topPadding: root.flat ? 0 : Theme.component.tile.emptyBackground.paddingTop bottomPadding: root.flat ? 0 : Theme.component.tile.emptyBackground.paddingBottom // File type icon. TODO. icon.width: Theme.component.button.icon.width icon.height: Theme.component.button.icon.height icon.color: Theme.component.icon.fill flat: true display: T.Button.TextUnderIcon checkable: false hoverEnabled: root.enabled overflowActionsIcon: "more-vertical" surfaceLevel: T.Surface.level property bool _indicatorVisible: indicatorVisibility === Tile.IndicatorHidden ? false : indicatorVisibility === Tile.IndicatorVisible ? true : (checked || (enabled && hovered) || visualFocus || _indicatorPressed || (enabled && _indicatorHovered) || _indicatorFocused) property bool _indicatorFocused property bool _indicatorHovered property bool _indicatorPressed Behavior on opacity { enabled: opacityAnim.duration > 0 OpacityAnimator { id: opacityAnim duration: Theme.component.tile.transitionDuration easing.type: Theme.component.tile.transitionTimingFunction } } contentItem: Item { implicitWidth: root.display === T.Button.TextUnderIcon ? root.flat ? Math.max(Theme.component.tile.orientationVertical.minWidth, root.implicitImageSize, Theme.component.tile.contentContainer.paddingLeft + tileInfo.implicitWidth + Theme.component.tile.contentContainer.paddingBottom) : Math.max(Theme.component.tile.orientationVertical.minWidth, root.implicitImageSize, Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingLeft + tileInfo.implicitWidth + Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingRight) : root.flat ? Math.max(Theme.component.tile.orientationHorizontal.minWidth, root.implicitImageSize + Theme.component.tile.contentContainer.paddingLeft + tileInfo.implicitWidth + Theme.component.tile.contentContainer.paddingRight) : Math.max(Theme.component.tile.orientationHorizontal.minWidth, root.implicitImageSize + Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingLeft + tileInfo.implicitWidth + Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingRight) implicitHeight: root.display === T.Button.TextUnderIcon ? root.flat ? Math.max(Theme.component.tile.orientationVertical.minHeight, Math.max(root.implicitImageSize, Theme.component.tile.orientationVertical.thumbnail.minHeight) + Theme.component.tile.contentContainer.paddingTop + tileInfo.implicitHeight + Theme.component.tile.contentContainer.paddingBottom) : Math.max(Theme.component.tile.orientationVertical.minHeight, Math.max(root.implicitImageSize, Theme.component.tile.orientationVertical.thumbnail.minHeight) + Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingTop + tileInfo.implicitHeight + Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingBottom) : root.flat ? Math.max(Theme.component.tile.orientationHorizontal.minHeight, root.implicitImageSize, Theme.component.tile.contentContainer.paddingTop + tileInfo.implicitHeight + Theme.component.tile.contentContainer.paddingBottom) : Math.max(Theme.component.tile.orientationHorizontal.minHeight, root.implicitImageSize, Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingTop + tileInfo.implicitHeight + Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingBottom) Image { id: tileImage Binding { target: root property: "implicitImageSize" value: root.implicitImageSizeBindingEnabled ? root.display === T.Button.TextUnderIcon ? (tileImage.implicitHeight/tileImage.implicitWidth)*(root.width - root.leftPadding - root.rightPadding) // binding loop if client does not provide explicit width for tile : (tileImage.implicitWidth/tileImage.implicitHeight)*(root.height - root.topPadding - root.bottomPadding) // binding loop if client does not provide explicit height for tile : root.display === T.Button.TextUnderIcon ? Math.max(Theme.component.tile.orientationVertical.thumbnail.minHeight, tileImage.implicitHeight) : Math.max(Theme.component.tile.orientationVertical.thumbnail.minHeight, tileImage.implicitWidth) // TODO: missing orientationHorizontal.thumbnail.minWidth token } // Note: we allow the image size to be larger than the implicit image size // as this allows for the case where the user sets the image size to be the // implicit image size of the first tile which happens to be larger than // the current tile's image's implicit size. // The image will be scaled up in this case (via PreserveAspectCrop). width: root.display === T.Button.TextUnderIcon ? parent.width : root.imageSize > 0 ? Math.min(root.imageSize, 0.7*parent.width) : Math.min(root.implicitImageSize, root.flat ? (parent.width - Theme.component.tile.contentContainer.paddingLeft - tileInfo.implicitWidth - Theme.component.tile.contentContainer.paddingRight) : (parent.width - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingLeft - tileInfo.implicitWidth - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingRight)) height: root.display !== T.Button.TextUnderIcon ? parent.height : root.imageSize > 0 ? Math.min(root.imageSize, 0.9*parent.height) : Math.min(root.implicitImageSize, root.flat ? (parent.height - Theme.component.tile.contentContainer.paddingTop - tileInfo.implicitHeight - Theme.component.tile.contentContainer.paddingBottom) : (parent.height - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingTop - tileInfo.implicitHeight - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingBottom)) source: root.source fillMode: Image.PreserveAspectCrop Rectangle { id: hoverGradient width: parent.width height: Math.min(tileImage.sourceSize.height, 60) // TODO: Tile-specific theme constant gradient: Gradient { GradientStop { position: 0.0; color: Qt.rgba(0.0, 0.0, 0.0, 0.85) } GradientStop { position: 1.0; color: Qt.rgba(0.0, 0.0, 0.0, 0.0) } } opacity: root._indicatorVisible ? 1.0 : 0.0 Behavior on opacity { enabled: gradientAnim.duration > 0 OpacityAnimator { id: gradientAnim duration: Theme.component.tile.transitionDuration easing.type: Theme.component.tile.transitionTimingFunction } } } Rectangle { id: actionsContainer visible: root.actionButtonsModel.count > 0 anchors { right: parent.right bottom: parent.bottom rightMargin: Theme.component.tile.actionsContainer.paddingRight bottomMargin: Theme.component.tile.actionsContainer.paddingBottom } width: root.display === T.Button.TextUnderIcon ? Theme.component.tile.actionsContainer.paddingLeft + actionButtonsLoader.implicitWidth + Theme.component.tile.actionsContainer.paddingRight : Theme.component.tile.orientationHorizontal.actionsContainer.minWidth height: root.display === T.Button.TextBesideIcon ? Theme.component.tile.actionsContainer.paddingBottom + actionButtonsLoader.implicitHeight + Theme.component.tile.actionsContainer.paddingTop : Theme.component.tile.orientationVertical.actionsContainer.minHeight color: Theme.component.tile.actionsContainer.backgroundColor radius: Theme.component.tile.actionsContainer.borderRadius border.width: root.display === T.Button.TextUnderIcon ? Theme.component.tile.orientationVertical.actionsContainer.borderBottomWidth : Theme.component.tile.orientationHorizontal.actionsContainer.borderRightWidth border.color: root.display === T.Button.TextUnderIcon ? Theme.component.tile.orientationVertical.actionsContainer.borderBottomColor : Theme.component.tile.orientationHorizontal.actionsContainer.borderRightColor Loader { id: actionButtonsLoader anchors { right: parent.right bottom: parent.bottom rightMargin: Theme.component.tile.actionsContainer.paddingRight bottomMargin: Theme.component.tile.actionsContainer.paddingBottom } active: actionsContainer.visible width: root.display === T.Button.TextUnderIcon && item ? item.implicitWidth : Theme.component.tile.actionsContainer.item.width height: root.display === T.Button.TextBesideIcon && item ? item.implicitHeight : Theme.component.tile.actionsContainer.item.height sourceComponent: root.display === T.Button.TextUnderIcon ? actionsContainer.actionsRow : actionsContainer.actionsColumn } property Component actionsRow: Component { Row { id: actionButtonsRow height: Theme.component.tile.actionsContainer.item.height visible: root.actionButtonsModel.count > 0 spacing: Theme.component.tile.actionsContainer.gap children: [ Repeater { model: root.actionButtonsModel } ] } } property Component actionsColumn: Component { Column { id: actionButtonsColumn width: Theme.component.tile.actionsContainer.item.width visible: root.actionButtonsModel.count > 0 spacing: Theme.component.tile.actionsContainer.gap children: [ Repeater { model: root.actionButtonsModel } ] } } } } TableLayoutColumn { id: tileInfo x: root.display === T.Button.TextUnderIcon ? root.flat ? Theme.component.tile.contentContainer.paddingLeft : Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingLeft : root.flat ? (tileImage.width + Theme.component.tile.contentContainer.paddingLeft) : (tileImage.width + Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingLeft) y: root.display === T.Button.TextUnderIcon ? root.flat ? (tileImage.height + Theme.component.tile.contentContainer.paddingTop) : (tileImage.height + Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingTop) : root.flat ? Theme.component.tile.contentContainer.paddingTop : Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingTop width: root.display === T.Button.TextUnderIcon ? root.flat ? (parent.width - Theme.component.tile.contentContainer.paddingLeft - Theme.component.tile.contentContainer.paddingRight) : (parent.width - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingLeft - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingRight) : root.flat ? (parent.width - tileImage.width - Theme.component.tile.contentContainer.paddingLeft - Theme.component.tile.contentContainer.paddingRight) : (parent.width - tileImage.width - Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingLeft - Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingRight) height: root.display === T.Button.TextUnderIcon ? root.flat ? (parent.height - tileImage.height - Theme.component.tile.contentContainer.paddingTop - Theme.component.tile.contentContainer.paddingBottom) : (parent.height - tileImage.height - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingTop - Theme.component.tile.orientationVertical.emptyBackground.contentContainer.paddingBottom) : root.flat ? (parent.height - Theme.component.tile.contentContainer.paddingTop - Theme.component.tile.contentContainer.paddingBottom) : (parent.height - Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingTop - Theme.component.tile.orientationHorizontal.emptyBackground.contentContainer.paddingBottom) Item { id: primaryLabelContainer visible: (root.text.length > 0 || overflowLoader.active) anchors { left: tileInfo.left right: tileInfo.right } implicitWidth: primaryTextLabel.anchors.leftMargin + primaryTextLabel.implicitWidth + primaryTextLabel.anchors.rightMargin + overflowLoader.implicitWidth T.TableLayout.minimumHeight: visible ? Theme.component.tile.title.paddingTop + primaryTextLabel.lineHeight + Theme.component.tile.title.paddingBottom : 0 T.TableLayout.preferredHeight: visible ? Theme.component.tile.title.paddingTop + primaryTextLabelHeightCalculator.height + Theme.component.tile.title.paddingBottom : 0 BodyBold { id: primaryTextLabel anchors { left: parent.left right: overflowLoader.left top: parent.top bottom: parent.bottom leftMargin: Theme.component.tile.title.paddingLeft rightMargin: Theme.component.tile.title.paddingRight topMargin: Theme.component.tile.title.paddingTop bottomMargin: Theme.component.tile.title.paddingBottom } text: root.text verticalAlignment: Text.AlignVCenter elide: Text.ElideRight wrapMode: Text.Wrap maximumLineCount: root.textMaximumLineCount > 0 ? root.textMaximumLineCount : 2147483647 } BodyBold { id: primaryTextLabelHeightCalculator visible: false anchors { left: parent.left right: overflowLoader.left top: parent.top topMargin: Theme.component.tile.title.paddingTop leftMargin: Theme.component.tile.title.paddingLeft rightMargin: Theme.component.tile.title.paddingRight } text: primaryTextLabel.text verticalAlignment: primaryTextLabel.verticalAlignment elide: primaryTextLabel.elide wrapMode: primaryTextLabel.wrapMode maximumLineCount: root.textMaximumLineCount > 0 ? root.textMaximumLineCount : 2147483647 } Loader { id: overflowLoader anchors { right: parent.right verticalCenter: primaryTextLabel.verticalCenter } active: root.overflowActions !== null && root.overflowActions.count > 0 sourceComponent: ActionIconButton { id: overflowMenuButton leftPadding: Theme.generic.spacing.xs // TODO: not currently defined rightPadding: Theme.generic.spacing.xs // TODO: not currently defined popupIndicatorVisible: false icon.name: root.overflowActionsIcon swellOnPress: false popup: DropdownPopup { id: overflowActionsPopup control: overflowMenuButton fixedWidth: false model: root.overflowActions delegate: DropdownDelegate { required property int index required text onClicked: { root.overflowActionClicked(index) overflowActionsPopup.close() } } } } } } Item { id: secondaryLabelContainer visible: root.secondaryText.length > 0 anchors { left: tileInfo.left right: tileInfo.right } T.TableLayout.minimumHeight: !visible ? 0 : primaryLabelContainer.visible ? secondaryTextLabel.lineHeight + Theme.component.tile.subTitle.paddingBottom : (Theme.component.tile.title.paddingTop + secondaryTextLabel.lineHeight + Theme.component.tile.subTitle.paddingBottom) T.TableLayout.preferredHeight: primaryLabelContainer.visible ? secondaryTextLabelHeightCalculator.height + Theme.component.tile.subTitle.paddingBottom : (Theme.component.tile.title.paddingTop + secondaryTextLabel.lineHeight + Theme.component.tile.subTitle.paddingBottom) CaptionRegular { id: secondaryTextLabel visible: root.secondaryText.length > 0 anchors { left: parent.left right: parent.right top: parent.top bottom: parent.bottom leftMargin: Theme.component.tile.subTitle.paddingLeft rightMargin: Theme.component.tile.subTitle.paddingRight topMargin: primaryLabelContainer.visible ? Theme.component.tile.subTitle.paddingTop : Theme.component.tile.title.paddingTop bottomMargin: Theme.component.tile.subTitle.paddingBottom } text: root.secondaryText verticalAlignment: Text.AlignVCenter elide: Text.ElideRight wrapMode: Text.Wrap maximumLineCount: root.secondaryTextMaximumLineCount > 0 ? root.secondaryTextMaximumLineCount : 2147483647 } CaptionRegular { id: secondaryTextLabelHeightCalculator visible: false anchors { left: parent.left right: parent.right top: parent.top topMargin: primaryLabelContainer.visible ? Theme.component.tile.subTitle.paddingTop : Theme.component.tile.title.paddingTop leftMargin: Theme.component.tile.subTitle.paddingLeft rightMargin: Theme.component.tile.subTitle.paddingRight } text: secondaryTextLabel.text verticalAlignment: secondaryTextLabel.verticalAlignment elide: secondaryTextLabel.elide wrapMode: secondaryTextLabel.wrapMode maximumLineCount: root.secondaryTextMaximumLineCount > 0 ? root.secondaryTextMaximumLineCount : 2147483647 } } TableLayoutLoader { id: additionalInfoLoader anchors { left: tileInfo.left right: tileInfo.right } T.TableLayout.minimumHeight: (additionalInfoLoader.active && root.additionalInfo !== null) ? primaryTextLabel.lineHeight : 0 T.TableLayout.preferredHeight: item ? item.implicitHeight : 0 T.TableLayout.verticalFillMode: T.TableLayout.Expanding clip: true sourceComponent: root.additionalInfo } } } additionalInfo: (root.version.length > 0 || (root.actionButtonsModel !== null && root.actionButtonsModel.count > 0) || (root.overflowActions !== null && root.overflowActions.count > 0) || (root.callToActionText.length > 0)) ? standardAdditionalInfo : null Component { id: standardAdditionalInfo Column { width: parent.width spacing: Theme.component.tile.subTitle.paddingBottom Tag { id: versionTag visible: root.version.length > 0 text: root.version font.pixelSize: secondaryTextLabel.font.pixelSize background.implicitHeight: 1 topPadding: Theme.component.tile.subTitle.paddingBottom bottomPadding: Theme.component.tile.subTitle.paddingBottom } Button { id: callToActionButton visible: root.callToActionType === Tile.CallToActionButton && root.callToActionText.length > 0 text: root.callToActionText onClicked: root.callToActionClicked() } TextLink { id: callToActionLink visible: root.callToActionType === Tile.CallToActionLink && root.callToActionText.length > 0 text: root.callToActionText onLinkActivated: root.callToActionClicked() } } } indicator: Rectangle { // Some of the CheckBox background color states are transparent, // leaving only the borders visible - and those can be very difficult // to see depending on the tile image content. // So, add a surface-level specific solid background behind. id: indicatorBackground z: 1 x: root.leftPadding // root padding + checkbox margin + (Theme.component.checkbox.container.height - checkbox.indicator.implicitHeight)/2 y: root.topPadding // root padding + checkbox margin + (Theme.component.checkbox.container.height - checkbox.indicator.implicitHeight)/2 implicitWidth: checkbox.indicator.implicitWidth implicitHeight: checkbox.indicator.implicitHeight color: root.surfaceLevel >= 300 ? Theme.component.tile.surfaceHigh.backgroundColor.default : Theme.component.tile.surfaceLow.backgroundColor.default visible: root._indicatorVisible CheckBox { id: checkbox anchors.centerIn: parent hoverEnabled: true checked: root.checked contentItem: null // No need for label etc. onClicked: root.indicatorClicked() onHoveredChanged: root._indicatorHovered = hovered onPressedChanged: root._indicatorPressed = pressed onVisualFocusChanged: root._indicatorFocused = visualFocus } } background: Item { implicitWidth: root.display === T.Button.TextUnderIcon ? Theme.component.tile.orientationVertical.minWidth : Theme.component.tile.orientationHorizontal.minWidth implicitHeight: root.display === T.Button.TextUnderIcon ? Theme.component.tile.orientationVertical.minHeight : Theme.component.tile.orientationHorizontal.minHeight BoxShadow { anchors.fill: parent offsetX: Theme.component.tile.boxShadowX.focus offsetY: Theme.component.tile.boxShadowY.focus blurRadius: Theme.component.tile.boxShadowBlur.focus spreadRadius: Theme.component.tile.boxShadowSpread.focus color: root.visualFocus ? Theme.component.tile.boxShadowColor.focus : "transparent" Behavior on color { enabled: shadowColorAnim.duration > 0 ColorAnimation { id: shadowColorAnim duration: Theme.component.tile.transitionDuration easing.type: Theme.component.tile.transitionTimingFunction } } } Rectangle { id: bgRect anchors.fill: parent color: { const level = root.surfaceLevel >= 300 ? "surfaceLow" : "surfaceHigh" const state = root.pressed ? "pressed" : ((root.enabled && root.hovered) || root._indicatorHovered) ? "hover" : "default" return root.checked ? Theme.component.tile.backgroundColor.selected[state] : root.flat ? Theme.component.tile[level].backgroundColor[state] : Theme.component.tile[level].emptyBackground.backgroundColor[state] } Behavior on color { enabled: bgColorAnim.duration > 0 PremultipliedColorAnimation { id: bgColorAnim duration: Theme.component.tile.transitionDuration easing.type: Theme.component.tile.transitionTimingFunction } } radius: Theme.component.tile.borderRadius border.width: root.checked ? Theme.component.tile.borderWidth.selected : Theme.component.tile.borderWidth.default border.color: root.checked ? Theme.component.tile.borderColor.selected : Theme.component.tile.borderColor.default } } }