init: working version

This commit is contained in:
Kiana Sheibani 2025-10-07 19:43:46 -04:00
commit 7d8d7dacae
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
109 changed files with 15066 additions and 0 deletions

8
custom/Anim.qml Normal file
View file

@ -0,0 +1,8 @@
import qs.config
import QtQuick
NumberAnimation {
duration: Config.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Config.anim.curves.standard
}

8
custom/CAnim.qml Normal file
View file

@ -0,0 +1,8 @@
import qs.config
import QtQuick
ColorAnimation {
duration: Config.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Config.anim.curves.standard
}

29
custom/ColoredIcon.qml Normal file
View file

@ -0,0 +1,29 @@
pragma ComponentBehavior: Bound
import Quickshell.Widgets
import QtQuick
IconImage {
id: root
required property color color
property color dominantColor
asynchronous: true
/* layer.enabled: true */
/* layer.effect: Colouriser { */
/* sourceColor: root.dominantColour */
/* colorizationColor: root.colour */
/* } */
/* layer.onEnabledChanged: { */
/* if (layer.enabled && status === Image.Ready) */
/* CUtils.getDominantColour(this, c => dominantColour = c); */
/* } */
/* onStatusChanged: { */
/* if (layer.enabled && status === Image.Ready) */
/* CUtils.getDominantColour(this, c => dominantColour = c); */
/* } */
}

View file

@ -0,0 +1,82 @@
pragma ComponentBehavior: Bound
import qs.config
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Shapes
BusyIndicator {
id: root
property color fg: Config.colors.primary
property color bg: Config.colors.container
background: null
contentItem: Shape {
id: shape
preferredRendererType: Shape.CurveRenderer
asynchronous: true
RotationAnimator on rotation {
from: 0
to: 180
running: root.visible && root.running
loops: Animation.Infinite
duration: Config.anim.durations.extraLarge
easing.type: Easing.Linear
easing.bezierCurve: Config.anim.curves.expressiveDefaultSpatial
}
ShapePath {
strokeWidth: Math.min(root.implicitWidth, root.implicitHeight) * 0.18
strokeColor: root.bg
fillColor: "transparent"
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: shape.width / 2
centerY: shape.height / 2
radiusX: root.implicitWidth / 2
radiusY: root.implicitHeight / 2
startAngle: 0
sweepAngle: 360
}
Behavior on strokeColor {
CAnim {}
}
}
ShapePath {
strokeWidth: Math.min(root.implicitWidth, root.implicitHeight) * 0.18
strokeColor: root.fg
fillColor: "transparent"
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: shape.width / 2
centerY: shape.height / 2
radiusX: root.implicitWidth / 2
radiusY: root.implicitHeight / 2
startAngle: -sweepAngle / 2
sweepAngle: 60
}
PathAngleArc {
centerX: shape.width / 2
centerY: shape.height / 2
radiusX: root.implicitWidth / 2
radiusY: root.implicitHeight / 2
startAngle: 180 - sweepAngle / 2
sweepAngle: 60
}
Behavior on strokeColor {
CAnim {}
}
}
}
}

View file

@ -0,0 +1,8 @@
import QtQuick
import Quickshell.Widgets
import qs.config
ClippingRectangle {
color: "transparent"
}

View file

@ -0,0 +1,156 @@
import qs.services
import qs.config
import QtQuick
import QtQuick.Controls
Slider {
id: root
required property string icon
required property color color
property real oldValue
property bool initializing: true
Timer {
id: initDelay
running: true
interval: 100
onTriggered: root.initializing = false
}
orientation: Qt.Vertical
background: CustomRect {
color: Config.colors.container
radius: 1000
CustomRect {
implicitWidth: root.orientation === Qt.Horizontal ? root.handle.x + root.handle.width : root.handle.width
y: root.orientation === Qt.Vertical ? root.handle.y : 0
implicitHeight: root.orientation === Qt.Vertical ? parent.height - y : root.handle.height
color: root.color
opacity: 0.8
radius: parent.radius
}
}
handle: Item {
id: handle
property bool moving: false
x: root.orientation === Qt.Horizontal ? root.visualPosition * (root.availableWidth - width) : 0
y: root.orientation === Qt.Vertical ? root.visualPosition * (root.availableHeight - height) : 0
implicitWidth: root.orientation === Qt.Horizontal ? root.height : root.width
implicitHeight: implicitWidth
Elevation {
anchors.fill: parent
radius: rect.radius
level: handleInteraction.containsMouse ? 2 : 1
}
CustomRect {
id: rect
anchors.fill: parent
color: root.color
radius: 1000
MouseArea {
id: handleInteraction
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.NoButton
}
MaterialIcon {
id: icon
property bool moving: handle.moving
property bool stoppingMove: false
function update(): void {
text = moving ? Qt.binding(() => Math.round(root.value * 100)) : Qt.binding(() => root.icon);
font.family = moving ? Config.font.family.sans : Config.font.family.material;
font.pointSize = moving ? Config.font.size.smaller : Config.font.size.larger;
}
animate: !moving && !stoppingMove
animateDuration: Config.anim.durations.small
text: root.icon
font.pointSize: Config.font.size.larger
color: Config.colors.primaryDark
anchors.centerIn: parent
Behavior on moving {
enabled: icon.moving !== targetValue
SequentialAnimation {
PropertyAction {
target: icon
property: "stoppingMove"
value: true
}
Anim {
target: icon
property: "scale"
from: 1
to: 0
duration: Config.anim.durations.small / 2
easing.bezierCurve: Config.anim.curves.standardAccel
}
ScriptAction {
script: icon.update()
}
Anim {
target: icon
property: "scale"
from: 0
to: 1
duration: Config.anim.durations.small / 2
easing.bezierCurve: Config.anim.curves.standardDecel
}
PropertyAction {
target: icon
property: "stoppingMove"
value: false
}
}
}
}
}
}
onPressedChanged: handle.moving = pressed
onValueChanged: {
if (Math.abs(value - oldValue) < 0.01)
return;
if (!initializing) {
oldValue = value;
handle.moving = true;
stateChangeDelay.restart();
}
}
Timer {
id: stateChangeDelay
interval: 600
onTriggered: {
if (!root.pressed)
handle.moving = false;
}
}
Behavior on value {
enabled: !root.initializing
Anim {
duration: Config.anim.durations.normal
}
}
}

14
custom/CustomListView.qml Normal file
View file

@ -0,0 +1,14 @@
import qs.custom
import QtQuick
ListView {
id: root
maximumFlickVelocity: 3000
rebound: Transition {
Anim {
properties: "x,y"
}
}
}

View file

@ -0,0 +1,22 @@
import QtQuick
MouseArea {
property int scrollAccumulatedY: 0
property int scrollThreshold: 160
function onWheel(event: WheelEvent): void {
}
onWheel: event => {
// Update accumulated scroll
if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY))
scrollAccumulatedY = 0;
scrollAccumulatedY += event.angleDelta.y;
// Trigger handler and reset if above threshold
if (Math.abs(scrollAccumulatedY) >= scrollThreshold) {
onWheel(event);
scrollAccumulatedY = 0;
}
}
}

7
custom/CustomRect.qml Normal file
View file

@ -0,0 +1,7 @@
import QtQuick
import qs.config
Rectangle {
color: "transparent"
}

107
custom/CustomScrollBar.qml Normal file
View file

@ -0,0 +1,107 @@
import qs.services
import qs.config
import QtQuick
import QtQuick.Templates
ScrollBar {
id: root
required property Flickable flickable
property bool shouldBeActive
property real nonAnimPosition
property bool animating
onHoveredChanged: {
if (hovered)
shouldBeActive = true;
else
shouldBeActive = flickable.moving;
}
onPositionChanged: {
if (position === nonAnimPosition)
animating = false;
else if (!animating)
nonAnimPosition = position;
}
position: nonAnimPosition
implicitWidth: 5
contentItem: CustomRect {
anchors.left: parent.left
anchors.right: parent.right
opacity: {
if (root.size === 1)
return 0;
if (fullMouse.pressed)
return 0.8;
if (mouse.containsMouse)
return 0.6;
if (root.policy === ScrollBar.AlwaysOn || root.shouldBeActive)
return 0.4;
return 0;
}
radius: 1000
color: Config.colors.secondary
MouseArea {
id: mouse
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
Behavior on opacity {
Anim {}
}
}
Connections {
target: root.flickable
function onMovingChanged(): void {
if (root.flickable.moving)
root.shouldBeActive = true;
else
hideDelay.restart();
}
}
Timer {
id: hideDelay
interval: 600
onTriggered: root.shouldBeActive = root.flickable.moving || root.hovered
}
CustomMouseArea {
id: fullMouse
anchors.fill: parent
preventStealing: true
onPressed: event => {
root.animating = true;
root.nonAnimPosition = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2));
}
onPositionChanged: event => root.nonAnimPosition = Math.max(0, Math.min(1 - root.size, event.y / root.height - root.size / 2))
function onWheel(event: WheelEvent): void {
root.animating = true;
if (event.angleDelta.y > 0)
root.nonAnimPosition = Math.max(0, root.nonAnimPosition - 0.1);
else if (event.angleDelta.y < 0)
root.nonAnimPosition = Math.min(1 - root.size, root.nonAnimPosition + 0.1);
}
}
Behavior on position {
enabled: !fullMouse.pressed
Anim {}
}
}

View file

@ -0,0 +1,5 @@
import Quickshell.Hyprland
GlobalShortcut {
appid: "quickshell"
}

59
custom/CustomSlider.qml Normal file
View file

@ -0,0 +1,59 @@
import qs.config
import qs.custom
import qs.services
import QtQuick
import QtQuick.Controls
Slider {
id: root
property color progressColor: Config.colors.primary
background: Item {
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.topMargin: root.implicitHeight / 4
anchors.bottomMargin: root.implicitHeight / 4
implicitWidth: root.handle.x - root.implicitHeight / 6
color: root.progressColor
radius: 1000
topRightRadius: root.implicitHeight / 15
bottomRightRadius: root.implicitHeight / 15
}
CustomRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.topMargin: root.implicitHeight / 4
anchors.bottomMargin: root.implicitHeight / 4
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6
color: Config.colors.container
radius: 1000
topLeftRadius: root.implicitHeight / 15
bottomLeftRadius: root.implicitHeight / 15
}
}
handle: CustomRect {
x: root.visualPosition * root.availableWidth - implicitWidth / 2
implicitWidth: root.implicitHeight / 3
implicitHeight: root.implicitHeight
color: Config.colors.secondary
radius: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
}

155
custom/CustomSwitch.qml Normal file
View file

@ -0,0 +1,155 @@
import qs.config
import qs.custom
import qs.services
import qs.util
import QtQuick
import QtQuick.Controls
import QtQuick.Shapes
Switch {
id: root
property real size: Config.font.size.normal * 1.5
property color bg: Config.colors.container
property color accent: Color.mute(Config.colors.accent)
implicitWidth: implicitIndicatorWidth
implicitHeight: implicitIndicatorHeight
indicator: CustomRect {
radius: 1000
color: root.checked ? root.accent : root.bg
implicitWidth: implicitHeight * 1.7
implicitHeight: size
CustomRect {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
radius: 1000
color: root.checked ? Config.colors.secondary : Config.colors.tertiary
x: root.checked ? parent.implicitWidth - nonAnimWidth : 0
implicitWidth: nonAnimWidth
implicitHeight: parent.implicitHeight
anchors.verticalCenter: parent.verticalCenter
CustomRect {
anchors.fill: parent
radius: parent.radius
color: root.checked ? Config.colors.tertiary : Config.colors.secondary
opacity: root.pressed ? 0.4 : root.hovered ? 0.2 : 0
Behavior on opacity {
Anim {}
}
}
Shape {
id: icon
property point start1: {
if (root.pressed)
return Qt.point(width * 0.2, height / 2);
if (root.checked)
return Qt.point(width * 0.2, height / 2);
return Qt.point(width * 0.15, height * 0.15);
}
property point end1: {
if (root.pressed) {
if (root.checked)
return Qt.point(width * 0.4, height / 2);
return Qt.point(width * 0.8, height / 2);
}
if (root.checked)
return Qt.point(width * 0.45, height * 0.7);
return Qt.point(width * 0.85, height * 0.85);
}
property point start2: {
if (root.pressed) {
if (root.checked)
return Qt.point(width * 0.4, height / 2);
return Qt.point(width * 0.2, height / 2);
}
if (root.checked)
return Qt.point(width * 0.45, height * 0.7);
return Qt.point(width * 0.15, height * 0.85);
}
property point end2: {
if (root.pressed)
return Qt.point(width * 0.8, height / 2);
if (root.checked)
return Qt.point(width * 0.9, height * 0.2);
return Qt.point(width * 0.85, height * 0.15);
}
anchors.centerIn: parent
width: height
height: parent.implicitHeight * 0.45
preferredRendererType: Shape.CurveRenderer
asynchronous: true
ShapePath {
strokeWidth: root.size * 0.1
strokeColor: Config.colors.primaryDark
fillColor: "transparent"
capStyle: ShapePath.RoundCap
startX: icon.start1.x
startY: icon.start1.y
PathLine {
x: icon.end1.x
y: icon.end1.y
}
PathMove {
x: icon.start2.x
y: icon.start2.y
}
PathLine {
x: icon.end2.x
y: icon.end2.y
}
Behavior on strokeColor {
CAnim {}
}
}
Behavior on start1 {
PropAnim {}
}
Behavior on end1 {
PropAnim {}
}
Behavior on start2 {
PropAnim {}
}
Behavior on end2 {
PropAnim {}
}
}
Behavior on x {
Anim {}
}
Behavior on implicitWidth {
Anim {}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
component PropAnim: PropertyAnimation {
duration: Config.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Config.anim.curves.standard
}
}

46
custom/CustomText.qml Normal file
View file

@ -0,0 +1,46 @@
import QtQuick
import Quickshell
import qs.config
Text {
id: root
// Either boolean or function taking old and new text
property var animate: false
property string animateProp: "opacity"
property real animateFrom: 0
property real animateTo: 1
property int animateDuration: Config.anim.durations.normal
renderType: Text.NativeRendering
textFormat: Text.PlainText
color: Config.colors.primary
linkColor: Config.colors.highlight
font.family: Config.font.family.sans
font.pointSize: Config.font.size.smaller
Behavior on text {
enabled: typeof root.animate === "boolean"
? root.animate : root.animate(root.text, targetValue)
SequentialAnimation {
Anim {
to: root.animateFrom
easing.bezierCurve: Config.anim.curves.standardAccel
}
PropertyAction {}
Anim {
to: root.animateTo
easing.bezierCurve: Config.anim.curves.standardDecel
}
}
}
component Anim: NumberAnimation {
target: root
property: root.animateProp
duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
}
}

View file

@ -0,0 +1,68 @@
pragma ComponentBehavior: Bound
import qs.config
import qs.custom
import qs.services
import QtQuick
import QtQuick.Controls
TextField {
id: root
color: Config.colors.secondary
placeholderTextColor: Config.colors.tertiary
font.family: Config.font.family.sans
font.pointSize: Config.font.size.smaller
renderType: TextField.NativeRendering
cursorVisible: !readOnly
background: null
cursorDelegate: CustomRect {
id: cursor
property bool disableBlink
implicitWidth: 2
color: Config.colors.primary
radius: 20
Connections {
target: root
function onCursorPositionChanged(): void {
if (root.activeFocus && root.cursorVisible) {
cursor.opacity = 1;
cursor.disableBlink = true;
enableBlink.restart();
}
}
}
Timer {
id: enableBlink
interval: 100
onTriggered: cursor.disableBlink = false
}
Timer {
running: root.activeFocus && root.cursorVisible && !cursor.disableBlink
repeat: true
triggeredOnStart: true
interval: 500
onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1
}
Binding {
when: !root.activeFocus || !root.cursorVisible
cursor.opacity: 0
}
Behavior on opacity {
Anim {
duration: Config.anim.durations.small
}
}
}
}

10
custom/CustomWindow.qml Normal file
View file

@ -0,0 +1,10 @@
import Quickshell
import Quickshell.Wayland
PanelWindow {
required property string name
WlrLayershell.namespace: `quickshell-${name}`
color: "transparent"
}

17
custom/Elevation.qml Normal file
View file

@ -0,0 +1,17 @@
import qs.config
import QtQuick
import QtQuick.Effects
RectangularShadow {
property int level
property real dp: [0, 1, 3, 6, 8, 12][level]
color: Qt.alpha(Config.colors.bg, 0.9)
blur: (dp * 5) ** 0.7
spread: -dp * 0.3 + (dp * 0.1) ** 2
offset.y: dp / 2
Behavior on dp {
Anim {}
}
}

49
custom/ExtraIndicator.qml Normal file
View file

@ -0,0 +1,49 @@
import qs.services
import qs.config
import QtQuick
CustomRect {
required property int extra
anchors.right: parent.right
anchors.margins: 12
color: Config.colors.inactive
radius: 12
implicitWidth: count.implicitWidth + 20
implicitHeight: count.implicitHeight + 10
opacity: extra > 0 ? 1 : 0
scale: extra > 0 ? 1 : 0.5
Elevation {
anchors.fill: parent
radius: parent.radius
opacity: parent.opacity
z: -1
level: 2
}
CustomText {
id: count
anchors.centerIn: parent
animate: false
text: qsTr("+%1").arg(parent.extra)
color: Config.colors.secondary
}
Behavior on opacity {
Anim {
duration: Config.anim.durations.expressiveFastSpatial
}
}
Behavior on scale {
Anim {
duration: Config.anim.durations.expressiveFastSpatial
easing.bezierCurve: Config.anim.curves.expressiveFastSpatial
}
}
}

18
custom/GlowEffect.qml Normal file
View file

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Effects
MultiEffect {
id: root
anchors.fill: source
property alias glowColor: root.shadowColor
shadowEnabled: true
blurMultiplier: 0.3
blurMax: 30
Behavior on shadowColor {
CAnim {}
}
}

32
custom/MaterialIcon.qml Normal file
View file

@ -0,0 +1,32 @@
import qs.config
import QtQuick
CustomText {
id: root
property real fill
property int grade: -25
property bool animateAxes: true
Behavior on font.variableAxes {
enabled: root.animateAxes && root.animate
SequentialAnimation {
PauseAnimation {
duration: root.animateDuration / 2
}
PropertyAction {}
}
}
color: Config.colors.secondary
animateProp: "scale"
font.family: "Material Symbols Rounded"
font.pointSize: Config.font.size.normal
font.variableAxes: ({
FILL: fill,
GRAD: grade,
opsz: fontInfo.pixelSize,
wght: fontInfo.weight
})
}

101
custom/StateLayer.qml Normal file
View file

@ -0,0 +1,101 @@
import QtQuick
import qs.config
import qs.custom
import qs.services
MouseArea {
id: root
property bool disabled
property color color: Config.colors.primary
property real radius: 1000
function onClicked(): void {}
cursorShape: disabled ? undefined : Qt.PointingHandCursor
hoverEnabled: true
onPressed: event => {
rippleAnim.x = event.x;
rippleAnim.y = event.y;
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(0, 0), dist(0, width), dist(width, 0), dist(width, height)));
rippleAnim.restart();
}
onClicked: event => !disabled && onClicked(event)
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: 0.1
}
ParallelAnimation {
Anim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
duration: Config.anim.durations.large
easing.bezierCurve: Config.anim.curves.standardDecel
}
Anim {
target: ripple
property: "opacity"
to: 0
duration: Config.anim.durations.large
easing.type: Easing.BezierSpline
easing.bezierCurve: Config.anim.curves.standardDecel
}
}
}
CustomClippingRect {
id: hoverLayer
anchors.fill: parent
property real alpha: root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.05 : 0
color: Qt.alpha(root.color, alpha)
radius: root.radius
Behavior on alpha {
Anim {
duration: Config.anim.durations.small
}
}
CustomRect {
id: ripple
radius: 1000
color: root.color
opacity: 0
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
}
}