initial port of ToolButton

wonder if will be possible to share something with Button,
but ToolButton is way more complicated
This commit is contained in:
Marco Martin 2014-07-25 14:21:05 +02:00
parent a059748fbc
commit a3266857ad
3 changed files with 210 additions and 410 deletions

View File

@ -19,427 +19,23 @@
*/
import QtQuick 2.1
import org.kde.plasma.core 2.0 as PlasmaCore
import QtQuick.Controls.Private 1.0
import QtQuick.Controls 1.0
import "private" as Private
import QtQuick.Controls 1.2 as QtControls
import "styles" as Styles
/**
* A plasma theme based toolbutton.
*/
Item {
Button {
id: button
// Commmon API
/**
* true if the button is flat.
*/
property bool flat: true
/**
* true if the button is checked.
*/
property bool checked: defaultAction ? defaultAction.checked : false
/**
* true if the button is checkable.
*/
property bool checkable: defaultAction ? defaultAction.checkable : false
/**
* type:string
* true if the button is currently pressed.
*/
property alias pressed: mouse.pressed
/**
* type:string
* The text of the button
*/
property alias text: label.text
/*! This property holds the button tooltip. */
property string tooltip
/**
* type:variant
*
* Sets the icon for the button. It can be any image from any protocol
* supported by the Image element, or a freedesktop-compatible icon name
*/
property alias iconSource: icon.source
/**
* type:font
* The font for the button
*/
property alias font: label.font
/**
* Emited when the button is clicked.
*/
signal clicked()
// Plasma extensiuons
property QtObject defaultAction
property bool flat: true
enabled: defaultAction == undefined || defaultAction.enabled
/**
* The smallest width this button can be to show all the contents
*/
//icon + label + left margin + right margin + spacing between icon and text
//here it assumesleft margin = right top = bottom, why?
// because the right and bottom margins can be disabled, so they would return 0, but their actual size is still needed for size hints
property real minimumWidth: Math.max(units.iconSizes.small, height) + label.implicitWidth + (delegate.margins ? (delegate.margins.left + delegate.margins.left + ((icon.valid) ? delegate.margins.left : 0)) : 0)
/**
* The smallest height this button can be to show all the contents
*/
property real minimumHeight: Math.max(units.iconSizes.small, label.implicitHeight) + (delegate.margins ? (delegate.margins.top + delegate.margins.top) : 0)
LayoutMirroring.enabled: (Qt.application.layoutDirection === Qt.RightToLeft)
LayoutMirroring.childrenInherit: true
implicitWidth: {
if (label.text.length == 0) {
height;
} else {
Math.max(theme.mSize(theme.defaultFont).width*12, minimumWidth);
}
}
implicitHeight: Math.max(theme.mSize(theme.defaultFont).height*1.6, minimumHeight)
// TODO: needs to define if there will be specific graphics for
// disabled buttons
opacity: enabled ? 1.0 : 0.5
Keys.onSpacePressed: internal.userPressed = true
Keys.onReturnPressed: internal.userPressed = true
Keys.onReleased: {
internal.userPressed = false
if (event.key == Qt.Key_Space ||
event.key == Qt.Key_Return)
internal.clickButton()
}
onActiveFocusChanged: {
if (activeFocus) {
shadow.state = "focus"
} else if (checked) {
shadow.state = "hidden"
} else {
shadow.state = "shadow"
}
}
QtObject {
id: internal
property bool userPressed: false
function clickButton()
{
if (!button.enabled) {
return
}
if (defaultAction && defaultAction.checkable) {
defaultAction.checked = !defaultAction.checked
} else if (button.checkable) {
button.checked = !button.checked
}
if (button.KeyNavigation.tab || button.KeyNavigation.backtab) {
// Only focus the button if it is set up for keyboard
// navigation. This avoid getting a strange focus frame around
// buttons which are usually not focusable, such as buttons in
// a toolbar.
button.forceActiveFocus();
}
button.clicked()
if (defaultAction) {
defaultAction.trigger()
}
}
}
Loader {
id: delegate
anchors.fill: parent
property QtObject margins: item.margins
property string shadowState: "shadow"
sourceComponent: {
if (label.text.length == 0 && button.width == button.height && (button.parent && button.parent.checkedButton === undefined) && !flat) {
return roundButtonComponent
} else {
return buttonComponent
}
}
}
Component {
id: buttonComponent
Item {
parent: delegate
anchors.fill: parent
property alias margins: surface.fixedMargins
property alias hasOverState: shadow.hasOverState
Private.ButtonShadow {
id: shadow
anchors.fill: parent
visible: !flat && (surface.enabledBorders == "AllBorders" || state == "hover" || state == "focus")
state: delegate.shadowState
}
PlasmaCore.FrameSvgItem {
id: surface
enabledBorders: {
if (flat || !button.parent ||
button.parent.width < button.parent.implicitWidth ||
button.parent.checkedButton === undefined ||
!bordersSvg.hasElement("pressed-hint-compose-over-border")) {
if (shadows !== null) {
shadows.destroy()
}
return "AllBorders"
}
var borders = new Array()
if (button.x == 0) {
borders.push("LeftBorder")
shadow.anchors.leftMargin = 0;
} else {
shadow.anchors.leftMargin = -1;
}
if (button.y == 0) {
borders.push("TopBorder")
shadow.anchors.topMargin = 0;
} else {
shadow.anchors.topMargin = -1;
}
if (button.x + button.width >= button.parent.width) {
borders.push("RightBorder")
shadow.anchors.rightMargin = 0;
} else {
shadow.anchors.rightMargin = -1;
}
if (button.y + button.height >= button.parent.height) {
borders.push("BottomBorder")
shadow.anchors.bottomMargin = 0;
} else {
shadow.anchors.bottomMargin = -1;
}
if (shadows === null) {
shadows = shadowsComponent.createObject(surface)
}
return borders.join("|")
}
anchors.fill: parent
imagePath: "widgets/button"
prefix: (internal.userPressed || checked) ? "pressed" : "normal"
//internal: if there is no hover status, don't paint on mouse over in touchscreens
opacity: (internal.userPressed || checked || !flat || (shadow.hasOverState && mouse.containsMouse && button.enabled)) ? 1 : 0
Behavior on opacity {
PropertyAnimation { duration: units.longDuration }
}
PlasmaCore.Svg {
id: bordersSvg
imagePath: "widgets/button"
}
property Item shadows
Component {
id: shadowsComponent
Item {
anchors.fill: parent
PlasmaCore.SvgItem {
svg: bordersSvg
width: naturalSize.width
elementId: surface.prefix+"-left"
visible: button.x > 0
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
margins: 1
leftMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
width: naturalSize.width
elementId: surface.prefix+"-right"
visible: button.x + button.width < button.parent.width
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 1
rightMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
height: naturalSize.height
elementId: surface.prefix+"-top"
visible: button.y > 0
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: 1
topMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
height: naturalSize.height
elementId: surface.prefix+"-bottom"
visible: button.y + button.height < button.parent.height
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 1
bottomMargin: -1
}
}
}
}
}
}
}
Component {
id: roundButtonComponent
Item {
id: roundButtonDelegate
parent: delegate
anchors.fill: parent
property QtObject margins: QtObject {
property int left: delegate.width/8
property int top: delegate.width/8
property int right: delegate.width/8
property int bottom: delegate.width/8
}
property alias hasOverState: roundShadow.hasOverState
Private.RoundShadow {
id: roundShadow
visible: !flat
anchors.fill: parent
state: delegate.shadowState
}
PlasmaCore.Svg {
id: buttonSvg
imagePath: "widgets/actionbutton"
}
PlasmaCore.SvgItem {
id: buttonItem
svg: buttonSvg
elementId: (internal.userPressed || checked) ? "pressed" : "normal"
width: Math.floor(parent.height/2) * 2
height: width
//internal: if there is no hover status, don't paint on mouse over in touchscreens
opacity: (internal.userPressed || checked || !flat || (roundShadow.hasOverState && mouse.containsMouse)) ? 1 : 0
Behavior on opacity {
PropertyAnimation { duration: units.longDuration }
}
}
}
}
Row {
anchors {
fill: parent
leftMargin: delegate.margins ? delegate.margins.left : 0
topMargin: delegate.margins ? delegate.margins.top : 0
rightMargin: delegate.margins ? delegate.margins.right : 0
bottomMargin: delegate.margins ? delegate.margins.bottom : 0
}
spacing: icon.valid && delegate.margins ? delegate.margins.left : 0
PlasmaCore.IconItem {
id: icon
anchors.verticalCenter: parent.verticalCenter
width: valid ? Math.min(Math.floor(parent.width/2) * 2, Math.floor(parent.height/2) * 2): 0
height: width
active: delegate.item.hasOverState && mouse.containsMouse
colorGroup: mouse.containsMouse || !button.flat ? PlasmaCore.Theme.ButtonColorGroup : PlasmaCore.Theme.NormalColorGroup
}
Label {
id: label
width: parent.width - icon.width - parent.spacing
height: parent.height
color: mouse.containsMouse || !button.flat ? theme.buttonTextColor : theme.textColor
Behavior on color { ColorAnimation { duration: units.shortDuration * 2 } }
horizontalAlignment: icon.valid ? Text.AlignLeft : Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: delegate.item.hasOverState
onPressed: internal.userPressed = true
onReleased: internal.userPressed = false
onCanceled: {
internal.userPressed = false
delegate.shadowState = "shadow"
Tooltip.hideText()
}
onClicked: internal.clickButton()
onEntered: {
if (delegate.item.hasOverState && !flat && !internal.userPressed && !checked) {
delegate.shadowState = "hover"
}
button.z += 2
}
onExited: {
if (!flat) {
if (button.activeFocus) {
delegate.shadowState = "focus"
} else if (checked) {
delegate.shadowState = "hidden"
} else {
delegate.shadowState = "shadow"
}
}
button.z -= 2
Tooltip.hideText()
}
Timer {
interval: 1000
running: mouse.containsMouse && !pressed && tooltip.length
onTriggered: Tooltip.showText(mouse, Qt.point(mouse.mouseX, mouse.mouseY), tooltip)
}
}
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.description: tooltip
function accessiblePressAction() {
internal.clickButton()
}
style: Styles.ToolButtonStyle {}
}

View File

@ -114,6 +114,8 @@ QtQuickControlStyle.ButtonStyle {
}
}
opacity: enabled ? 1.0 : 0.5
Private.ButtonShadow {
anchors.fill: parent
state: {

View File

@ -0,0 +1,202 @@
/*
* Copyright 2014 by Marco Martin <mart@kde.org>
* Copyright 2014 by David Edmundson <davidedmundson@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/
import QtQuick 2.0
import QtQuick.Controls.Styles 1.1 as QtQuickControlStyle
import QtQuick.Layouts 1.1
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.components 2.0 as PlasmaComponents
import "../private" as Private
QtQuickControlStyle.ButtonStyle {
id: style
property int minimumWidth
property int minimumHeight
label: RowLayout {
id: buttonContent
spacing: units.smallSpacing
Layout.preferredHeight: Math.max(units.iconSizes.small, label.implicitHeight)
property real minimumWidth: Layout.minimumWidth + style.padding.left + style.padding.right
onMinimumWidthChanged: {
if (control.minimumWidth !== undefined) {
style.minimumWidth = minimumWidth;
control.minimumWidth = minimumWidth;
}
}
property real minimumHeight: Layout.preferredHeight + style.padding.top + style.padding.bottom
onMinimumHeightChanged: {
if (control.minimumHeight !== undefined) {
style.minimumHeight = minimumHeight;
control.minimumHeight = minimumHeight;
}
}
PlasmaCore.IconItem {
id: icon
source: control.iconName || control.iconSource
anchors.verticalCenter: parent.verticalCenter
Layout.minimumWidth: valid ? parent.height: 0
Layout.maximumWidth: Layout.minimumWidth
visible: valid
Layout.minimumHeight: Layout.minimumWidth
Layout.maximumHeight: Layout.minimumWidth
active: control.hovered
colorGroup: PlasmaCore.Theme.ButtonColorGroup
}
PlasmaComponents.Label {
id: label
Layout.minimumWidth: implicitWidth
text: control.text
font: control.font
visible: control.text != ""
Layout.fillWidth: true
height: parent.height
color: theme.buttonTextColor
horizontalAlignment: icon.valid ? Text.AlignLeft : Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
PlasmaExtras.ConditionalLoader {
id: arrow
when: control.menu !== null
visible: when
Layout.minimumWidth: units.iconSizes.small
Layout.maximumWidth: Layout.minimumWidth
Layout.alignment: Qt.AlignVCenter
height: width
source: Component {
PlasmaCore.SvgItem {
visible: control.menu !== null
anchors.fill: parent
svg: PlasmaCore.Svg { imagePath: "widgets/arrows" }
elementId: "down-arrow"
}
}
}
}
background: Item {
implicitHeight: Math.floor(Math.max(theme.mSize(theme.defaultFont).height*1.6, style.minimumHeight))
implicitWidth: {
if (control.text.length == 0) {
height;
} else {
Math.max(theme.mSize(theme.defaultFont).width*12, style.minimumWidth);
}
}
Private.ButtonShadow {
anchors.fill: parent
state: {
if (control.pressed) {
return "hidden"
} else if (control.hovered) {
return "hover"
} else if (control.activeFocus) {
return "focus"
} else {
return "shadow"
}
}
}
//This code is duplicated here and Button and ToolButton
//maybe we can make an AbstractButton class?
PlasmaCore.FrameSvgItem {
id: surfaceNormal
anchors.fill: parent
imagePath: "widgets/button"
prefix: "normal"
}
PlasmaCore.FrameSvgItem {
id: surfacePressed
anchors.fill: parent
imagePath: "widgets/button"
prefix: "pressed"
opacity: 0
}
state: (control.pressed || control.checked ? "pressed" : (control.hovered ? "hover" : "normal"))
states: [
State { name: "normal"
PropertyChanges {
target: surfaceNormal
opacity: control.flat ? 0 : 1
}
PropertyChanges {
target: surfacePressed
opacity: 0
}
},
State { name: "hover"
PropertyChanges {
target: surfaceNormal
opacity: 1
}
PropertyChanges {
target: surfacePressed
opacity: 0
}
},
State { name: "pressed"
PropertyChanges {
target: surfaceNormal
opacity: 0
}
PropertyChanges {
target: surfacePressed
opacity: 1
}
}
]
transitions: [
Transition {
//Cross fade from pressed to normal
ParallelAnimation {
NumberAnimation { target: surfaceNormal; property: "opacity"; duration: 100 }
NumberAnimation { target: surfacePressed; property: "opacity"; duration: 100 }
}
}
]
Component.onCompleted: {
padding.top = surfaceNormal.margins.top
padding.left = surfaceNormal.margins.left
padding.right = surfaceNormal.margins.right
padding.bottom = surfaceNormal.margins.bottom
}
}
}