aboutsummaryrefslogtreecommitdiffstats
path: root/config/quickshell/modules/common/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'config/quickshell/modules/common/widgets')
-rw-r--r--config/quickshell/modules/common/widgets/CliphistImage.qml101
-rw-r--r--config/quickshell/modules/common/widgets/DialogButton.qml38
-rw-r--r--config/quickshell/modules/common/widgets/MaterialSymbol.qml27
-rw-r--r--config/quickshell/modules/common/widgets/PointingHandInteraction.qml7
-rw-r--r--config/quickshell/modules/common/widgets/RippleButton.qml185
-rw-r--r--config/quickshell/modules/common/widgets/RoundCorner.qml64
-rw-r--r--config/quickshell/modules/common/widgets/StyledRectangularShadow.qml13
-rw-r--r--config/quickshell/modules/common/widgets/StyledText.qml13
-rw-r--r--config/quickshell/modules/common/widgets/StyledTextArea.qml15
-rw-r--r--config/quickshell/modules/common/widgets/StyledToolTip.qml60
10 files changed, 523 insertions, 0 deletions
diff --git a/config/quickshell/modules/common/widgets/CliphistImage.qml b/config/quickshell/modules/common/widgets/CliphistImage.qml
new file mode 100644
index 00000000..9de34450
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/CliphistImage.qml
@@ -0,0 +1,101 @@
+import "root:/modules/common"
+import "root:/modules/common/widgets"
+import "root:/services"
+import "root:/modules/common/functions/string_utils.js" as StringUtils
+import "root:/modules/common/functions/file_utils.js" as FileUtils
+import Qt5Compat.GraphicalEffects
+import Qt.labs.platform
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell.Io
+import Quickshell.Widgets
+import Quickshell.Hyprland
+
+Rectangle {
+ id: root
+ property string entry
+ property real maxWidth
+ property real maxHeight
+
+ property string imageDecodePath: Directories.cliphistDecode
+ property string imageDecodeFileName: `${entryNumber}`
+ property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`
+ property string source
+
+ property int entryNumber: {
+ if (!root.entry) return 0
+ const match = root.entry.match(/^(\d+)\t/)
+ return match ? parseInt(match[1]) : 0
+ }
+ property int imageWidth: {
+ if (!root.entry) return 0
+ const match = root.entry.match(/(\d+)x(\d+)/)
+ return match ? parseInt(match[1]) : 0
+ }
+ property int imageHeight: {
+ if (!root.entry) return 0
+ const match = root.entry.match(/(\d+)x(\d+)/)
+ return match ? parseInt(match[2]) : 0
+ }
+ property real scale: {
+ return Math.min(
+ root.maxWidth / imageWidth,
+ root.maxHeight / imageHeight,
+ 1
+ )
+ }
+
+ color: Appearance.colors.colLayer1
+ radius: Appearance.rounding.small
+ implicitHeight: imageHeight * scale
+ implicitWidth: imageWidth * scale
+
+ Component.onCompleted: {
+ decodeImageProcess.running = true
+ }
+
+ Process {
+ id: decodeImageProcess
+ command: ["bash", "-c",
+ `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | cliphist decode > '${imageDecodeFilePath}'`
+ ]
+ onExited: (exitCode, exitStatus) => {
+ if (exitCode === 0) {
+ root.source = imageDecodeFilePath
+ } else {
+ console.error("[CliphistImage] Failed to decode image for entry:", root.entry)
+ root.source = ""
+ }
+ }
+ }
+
+ Component.onDestruction: {
+ Hyprland.dispatch(`exec bash -c "[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'"`)
+ }
+
+ Image {
+ id: image
+ anchors.fill: parent
+
+ source: Qt.resolvedUrl(root.source)
+ fillMode: Image.PreserveAspectFit
+ antialiasing: true
+ asynchronous: true
+
+ width: root.imageWidth * root.scale
+ height: root.imageHeight * root.scale
+ sourceSize.width: width
+ sourceSize.height: height
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ width: image.width
+ height: image.height
+ radius: root.radius
+ }
+ }
+ }
+}
+
diff --git a/config/quickshell/modules/common/widgets/DialogButton.qml b/config/quickshell/modules/common/widgets/DialogButton.qml
new file mode 100644
index 00000000..b799336a
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/DialogButton.qml
@@ -0,0 +1,38 @@
+import "root:/modules/common"
+import "root:/modules/common/functions/color_utils.js" as ColorUtils
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+
+/**
+ * Material 3 dialog button. See https://m3.material.io/components/dialogs/overview
+ */
+RippleButton {
+ id: button
+
+ property string buttonText
+ implicitHeight: 30
+ implicitWidth: buttonTextWidget.implicitWidth + 15 * 2
+ buttonRadius: Appearance?.rounding.full ?? 9999
+
+ property color colEnabled: Appearance?.colors.colPrimary
+ property color colDisabled: Appearance?.m3colors.m3borderPrimary
+
+ contentItem: StyledText {
+ id: buttonTextWidget
+ anchors.fill: parent
+ anchors.leftMargin: 15
+ anchors.rightMargin: 15
+ text: buttonText
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Appearance?.font.pixelSize.textBase ?? 12
+ color: button.enabled ? button.colEnabled : button.colDisabled
+
+ Behavior on color {
+ animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
+ }
+ }
+
+}
diff --git a/config/quickshell/modules/common/widgets/MaterialSymbol.qml b/config/quickshell/modules/common/widgets/MaterialSymbol.qml
new file mode 100644
index 00000000..214f838e
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/MaterialSymbol.qml
@@ -0,0 +1,27 @@
+import "root:/modules/common/"
+import QtQuick
+
+Text {
+ id: root
+ property real iconSize: Appearance?.font.pixelSize.textBase ?? 16
+ property real fill: 0
+ renderType: Text.NativeRendering
+ font.hintingPreference: Font.PreferFullHinting
+ verticalAlignment: Text.AlignVCenter
+ font.family: Appearance?.font.family.iconFont ?? "Material Symbols Rounded"
+ font.pixelSize: iconSize
+ color: Appearance.m3colors.m3primaryText
+
+ Behavior on fill {
+ NumberAnimation {
+ duration: Appearance?.animation.elementMoveFast.duration ?? 200
+ easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
+ easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
+ }
+ }
+
+ font.variableAxes: {
+ "FILL": fill,
+ "opsz": iconSize,
+ }
+}
diff --git a/config/quickshell/modules/common/widgets/PointingHandInteraction.qml b/config/quickshell/modules/common/widgets/PointingHandInteraction.qml
new file mode 100644
index 00000000..cf8b065f
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/PointingHandInteraction.qml
@@ -0,0 +1,7 @@
+import QtQuick
+
+MouseArea {
+ anchors.fill: parent
+ onPressed: (mouse) => mouse.accepted = false
+ cursorShape: Qt.PointingHandCursor
+} \ No newline at end of file
diff --git a/config/quickshell/modules/common/widgets/RippleButton.qml b/config/quickshell/modules/common/widgets/RippleButton.qml
new file mode 100644
index 00000000..cd7762b9
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/RippleButton.qml
@@ -0,0 +1,185 @@
+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
+ }
+}
diff --git a/config/quickshell/modules/common/widgets/RoundCorner.qml b/config/quickshell/modules/common/widgets/RoundCorner.qml
new file mode 100644
index 00000000..c9a2827a
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/RoundCorner.qml
@@ -0,0 +1,64 @@
+import QtQuick 2.9
+
+Item {
+ id: root
+
+ property int size: 25
+ property color color: "#000000"
+
+ onColorChanged: {
+ canvas.requestPaint();
+ }
+
+ property QtObject cornerEnum: QtObject {
+ property int topLeft: 0
+ property int topRight: 1
+ property int bottomLeft: 2
+ property int bottomRight: 3
+ }
+
+ property int corner: cornerEnum.topLeft // Default to TopLeft
+
+ width: size
+ height: size
+
+ Canvas {
+ id: canvas
+
+ anchors.fill: parent
+ antialiasing: true
+
+ onPaint: {
+ var ctx = getContext("2d");
+ var r = root.size;
+
+ ctx.beginPath();
+ switch (root.corner) {
+ case cornerEnum.topLeft:
+ ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
+ ctx.lineTo(0, 0);
+ break;
+ case cornerEnum.topRight:
+ ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
+ ctx.lineTo(r, 0);
+ break;
+ case cornerEnum.bottomLeft:
+ ctx.arc(r, 0, r, Math.PI / 2, Math.PI);
+ ctx.lineTo(0, r);
+ break;
+ case cornerEnum.bottomRight:
+ ctx.arc(0, 0, r, 0, Math.PI / 2);
+ ctx.lineTo(r, r);
+ break;
+ }
+ ctx.closePath();
+ ctx.fillStyle = root.color;
+ ctx.fill();
+ }
+ }
+
+ Behavior on size {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+}
diff --git a/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml b/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml
new file mode 100644
index 00000000..6e1f2e16
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/StyledRectangularShadow.qml
@@ -0,0 +1,13 @@
+import QtQuick
+import QtQuick.Effects
+import "root:/modules/common"
+
+RectangularShadow {
+ required property var target
+ anchors.fill: target
+ radius: target.radius
+ blur: 1.2 * Appearance.sizes.elevationMargin
+ spread: 1
+ color: Appearance.colors.colShadow
+ cached: true
+}
diff --git a/config/quickshell/modules/common/widgets/StyledText.qml b/config/quickshell/modules/common/widgets/StyledText.qml
new file mode 100644
index 00000000..988c136d
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/StyledText.qml
@@ -0,0 +1,13 @@
+import "root:/modules/common"
+import QtQuick
+
+Text {
+ renderType: Text.NativeRendering
+ verticalAlignment: Text.AlignVCenter
+ font {
+ hintingPreference: Font.PreferFullHinting
+ family: Appearance?.font.family.uiFont ?? "sans-serif"
+ pixelSize: Appearance?.font.pixelSize.textBase ?? 15
+ }
+ color: Appearance?.m3colors.m3primaryText ?? "black"
+}
diff --git a/config/quickshell/modules/common/widgets/StyledTextArea.qml b/config/quickshell/modules/common/widgets/StyledTextArea.qml
new file mode 100644
index 00000000..af3cf34d
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/StyledTextArea.qml
@@ -0,0 +1,15 @@
+import "root:/modules/common"
+import QtQuick
+import QtQuick.Controls
+
+TextArea {
+ renderType: Text.NativeRendering
+ selectedTextColor: Appearance.m3colors.m3selectionText
+ selectionColor: Appearance.m3colors.m3selectionBackground
+ placeholderTextColor: Appearance.m3colors.m3borderPrimary
+ font {
+ family: Appearance?.font.family.uiFont ?? "sans-serif"
+ pixelSize: Appearance?.font.pixelSize.textBase ?? 15
+ hintingPreference: Font.PreferFullHinting
+ }
+}
diff --git a/config/quickshell/modules/common/widgets/StyledToolTip.qml b/config/quickshell/modules/common/widgets/StyledToolTip.qml
new file mode 100644
index 00000000..aaaad813
--- /dev/null
+++ b/config/quickshell/modules/common/widgets/StyledToolTip.qml
@@ -0,0 +1,60 @@
+import "root:/modules/common"
+import "root:/modules/common/widgets"
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ToolTip {
+ id: root
+ property string content
+ property bool extraVisibleCondition: true
+ property bool alternativeVisibleCondition: false
+ property bool internalVisibleCondition: {
+ const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
+ return ans
+ }
+ verticalPadding: 5
+ horizontalPadding: 10
+ opacity: internalVisibleCondition ? 1 : 0
+ visible: opacity > 0
+
+ Behavior on opacity {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ background: null
+
+ contentItem: Item {
+ id: contentItemBackground
+ implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding
+ implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding
+
+ Rectangle {
+ id: backgroundRectangle
+ anchors.bottom: contentItemBackground.bottom
+ anchors.horizontalCenter: contentItemBackground.horizontalCenter
+ color: Appearance?.m3colors.colTooltip ?? "#3C4043"
+ radius: Appearance?.rounding.verysmall ?? 7
+ width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0
+ height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0
+ clip: true
+
+ Behavior on width {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on height {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ StyledText {
+ id: tooltipTextObject
+ anchors.centerIn: parent
+ text: content
+ font.pixelSize: Appearance?.font.pixelSize.textSmall ?? 14
+ font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
+ color: Appearance?.m3colors.colOnTooltip ?? "#FFFFFF"
+ wrapMode: Text.Wrap
+ }
+ }
+ }
+} \ No newline at end of file
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage