185 lines
6 KiB
QML
185 lines
6 KiB
QML
import "root:/modules/common"
|
|
import "root:/modules/common/widgets"
|
|
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
|
import Qt5Compat.GraphicalEffects
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import Quickshell.Io
|
|
import Quickshell.Widgets
|
|
|
|
/**
|
|
* A button with ripple effect similar to in Material Design.
|
|
*/
|
|
Button {
|
|
id: root
|
|
property bool toggled
|
|
property string buttonText
|
|
property real buttonRadius: Appearance?.rounding?.small ?? 4
|
|
property real buttonRadiusPressed: buttonRadius
|
|
property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
|
|
property int rippleDuration: 1200
|
|
property bool rippleEnabled: true
|
|
property var downAction // When left clicking (down)
|
|
property var releaseAction // When left clicking (release)
|
|
property var altAction // When right clicking
|
|
property var middleClickAction // When middle clicking
|
|
|
|
property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent"
|
|
property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED"
|
|
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
|
|
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
|
|
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
|
|
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
|
|
|
|
property color buttonColor: root.enabled ? (root.toggled ?
|
|
(root.hovered ? colBackgroundToggledHover :
|
|
colBackgroundToggled) :
|
|
(root.hovered ? colBackgroundHover :
|
|
colBackground)) : colBackground
|
|
property color rippleColor: root.toggled ? colRippleToggled : colRipple
|
|
|
|
function startRipple(x, y) {
|
|
const stateY = buttonBackground.y;
|
|
rippleAnim.x = x;
|
|
rippleAnim.y = y - stateY;
|
|
|
|
const dist = (ox,oy) => ox*ox + oy*oy
|
|
const stateEndY = stateY + buttonBackground.height
|
|
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
|
|
|
|
rippleFadeAnim.complete();
|
|
rippleAnim.restart();
|
|
}
|
|
|
|
component RippleAnim: NumberAnimation {
|
|
duration: rippleDuration
|
|
easing.type: Appearance?.animation.elementMoveEnter.type
|
|
easing.bezierCurve: Appearance?.animationCurves.standardDecel
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onPressed: (event) => {
|
|
if(event.button === Qt.RightButton) {
|
|
if (root.altAction) root.altAction();
|
|
return;
|
|
}
|
|
if(event.button === Qt.MiddleButton) {
|
|
if (root.middleClickAction) root.middleClickAction();
|
|
return;
|
|
}
|
|
root.down = true
|
|
if (root.downAction) root.downAction();
|
|
if (!root.rippleEnabled) return;
|
|
const {x,y} = event
|
|
startRipple(x, y)
|
|
}
|
|
onReleased: (event) => {
|
|
root.down = false
|
|
if (event.button != Qt.LeftButton) return;
|
|
if (root.releaseAction) root.releaseAction();
|
|
root.click() // Because the MouseArea already consumed the event
|
|
if (!root.rippleEnabled) return;
|
|
rippleFadeAnim.restart();
|
|
}
|
|
onCanceled: (event) => {
|
|
root.down = false
|
|
if (!root.rippleEnabled) return;
|
|
rippleFadeAnim.restart();
|
|
}
|
|
}
|
|
|
|
RippleAnim {
|
|
id: rippleFadeAnim
|
|
target: ripple
|
|
property: "opacity"
|
|
to: 0
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: rippleAnim
|
|
|
|
property real x
|
|
property real y
|
|
property real radius
|
|
|
|
PropertyAction {
|
|
target: ripple
|
|
property: "x"
|
|
value: rippleAnim.x
|
|
}
|
|
PropertyAction {
|
|
target: ripple
|
|
property: "y"
|
|
value: rippleAnim.y
|
|
}
|
|
PropertyAction {
|
|
target: ripple
|
|
property: "opacity"
|
|
value: 1
|
|
}
|
|
ParallelAnimation {
|
|
RippleAnim {
|
|
target: ripple
|
|
properties: "implicitWidth,implicitHeight"
|
|
from: 0
|
|
to: rippleAnim.radius * 2
|
|
}
|
|
}
|
|
}
|
|
|
|
background: Rectangle {
|
|
id: buttonBackground
|
|
radius: root.buttonEffectiveRadius
|
|
implicitHeight: 50
|
|
|
|
color: root.buttonColor
|
|
Behavior on color {
|
|
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
|
}
|
|
|
|
layer.enabled: true
|
|
layer.effect: OpacityMask {
|
|
maskSource: Rectangle {
|
|
width: buttonBackground.width
|
|
height: buttonBackground.height
|
|
radius: root.buttonEffectiveRadius
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: ripple
|
|
width: ripple.implicitWidth
|
|
height: ripple.implicitHeight
|
|
opacity: 0
|
|
|
|
property real implicitWidth: 0
|
|
property real implicitHeight: 0
|
|
|
|
Behavior on opacity {
|
|
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
|
|
}
|
|
|
|
RadialGradient {
|
|
anchors.fill: parent
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: root.rippleColor }
|
|
GradientStop { position: 0.3; color: root.rippleColor }
|
|
GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }
|
|
}
|
|
}
|
|
|
|
transform: Translate {
|
|
x: -ripple.width / 2
|
|
y: -ripple.height / 2
|
|
}
|
|
}
|
|
}
|
|
|
|
contentItem: StyledText {
|
|
text: root.buttonText
|
|
}
|
|
}
|