diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/dismiss.svg b/dots/.config/quickshell/ii/assets/icons/fluent/dismiss.svg
new file mode 100644
index 000000000..3cb3656dc
--- /dev/null
+++ b/dots/.config/quickshell/ii/assets/icons/fluent/dismiss.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml
index 017d8f931..90d0cc007 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml
@@ -10,8 +10,16 @@ BarButton {
required property string iconName
property bool separateLightDark: false
+ leftInset: 2
+ rightInset: 2
implicitWidth: height - topInset - bottomInset + leftInset + rightInset
+ onDownChanged: {
+ scaleAnim.duration = root.down ? 150 : 200
+ scaleAnim.easing.bezierCurve = root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut
+ contentItem.scale = root.down ? 5/6 : 1 // If/When we do dragging, the scale is 1.25
+ }
+
contentItem: Item {
id: contentItem
anchors.centerIn: root.background
@@ -19,12 +27,10 @@ BarButton {
implicitHeight: iconWidget.implicitHeight
implicitWidth: iconWidget.implicitWidth
- scale: root.down ? 5/6 : 1 // If/When we do dragging, the scale is 1.25
Behavior on scale {
NumberAnimation {
- duration: 90
+ id: scaleAnim
easing.type: Easing.BezierSpline
- easing.bezierCurve: root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut
}
}
@@ -32,6 +38,7 @@ BarButton {
id: iconWidget
anchors.centerIn: parent
iconName: root.iconName
+ separateLightDark: root.separateLightDark
}
}
}
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/AppIcon.qml b/dots/.config/quickshell/ii/modules/waffle/bar/AppIcon.qml
index 02c1da144..fc5c75426 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/AppIcon.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/AppIcon.qml
@@ -5,11 +5,13 @@ import qs.modules.common
import qs.modules.waffle.looks
Kirigami.Icon {
- id: iconWidget
+ id: root
required property string iconName
+ property bool separateLightDark: false
- implicitWidth: 26
- implicitHeight: 26
+ property real implicitSize: 26
+ implicitWidth: implicitSize
+ implicitHeight: implicitSize
roundToIconSize: false
source: `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg`
fallback: root.iconName
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml
index 20929b1e6..52e5164aa 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml
@@ -11,17 +11,9 @@ Button {
Layout.fillHeight: true
topInset: 4
bottomInset: 4
-
- property color borderColor: ColorUtils.transparentize(Looks.colors.bg1Border, ((root.hovered && !root.down) || root.checked) ? Looks.fluentContentTransparency : 1)
- Behavior on borderColor {
- animation: Looks.transition.color.createObject(this)
- }
- onBorderColorChanged: {
- borderCanvas.requestPaint();
- }
- background: Rectangle {
- id: background
+ background: AcrylicRectangle {
+ shiny: ((root.hovered && !root.down) || root.checked)
color: {
if (root.down) {
return Looks.colors.bg1Active
@@ -31,48 +23,5 @@ Button {
return ColorUtils.transparentize(Looks.colors.bg1)
}
}
- radius: Looks.radius.medium
- Behavior on color {
- animation: Looks.transition.color.createObject(this)
- }
-
- // Top 1px border with color
- Canvas {
- id: borderCanvas
- anchors.fill: parent
- onPaint: {
- var ctx = getContext("2d");
- ctx.clearRect(0, 0, width, height);
-
- var borderColor = root.borderColor;
-
- var r = background.radius;
- var fadeLength = Math.max(1, r);
- var fadeLengthPercent = fadeLength / width;
-
- // Compute normalized stops
- var leftFadeStop = fadeLengthPercent;
- var rightFadeStop = 1 - fadeLengthPercent;
-
- var grad = ctx.createLinearGradient(0, 0, width, 0);
- grad.addColorStop(0, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));
- grad.addColorStop(leftFadeStop, borderColor);
- grad.addColorStop(rightFadeStop, borderColor);
- grad.addColorStop(1, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));
-
- ctx.strokeStyle = grad;
- ctx.lineWidth = 1;
-
- ctx.beginPath();
- ctx.moveTo(r, 0.5);
- ctx.lineTo(width - r, 0.5);
- // Top-right curve
- ctx.arcTo(width, 0.5, width, r + 0.5, r);
- // Top-left curve
- ctx.moveTo(width - r, 0.5);
- ctx.arcTo(0, 0.5, 0, r + 0.5, r);
- ctx.stroke();
- }
- }
}
}
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/TaskAppButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/TaskAppButton.qml
index 241796f0f..7363b0387 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/TaskAppButton.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/TaskAppButton.qml
@@ -3,15 +3,23 @@ import QtQuick.Layouts
import qs.services
import qs.modules.common
import qs.modules.waffle.looks
+import Quickshell
AppButton {
id: root
- required property var toplevel
- readonly property bool isSeparator: toplevel.appId === "SEPARATOR"
- readonly property var desktopEntry: DesktopEntries.heuristicLookup(toplevel.appId)
+ required property var appEntry
+ readonly property bool isSeparator: appEntry.appId === "SEPARATOR"
+ readonly property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId)
- Layout.fillHeight: true
+ signal hoverPreviewRequested()
- iconName: toplevel.appId
+ iconName: AppSearch.guessIcon(appEntry.appId)
+ Timer {
+ running: root.hovered
+ interval: 250
+ onTriggered: {
+ root.hoverPreviewRequested()
+ }
+ }
}
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/TaskPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/TaskPreview.qml
new file mode 100644
index 000000000..3c03563a3
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/TaskPreview.qml
@@ -0,0 +1,128 @@
+import QtQuick
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+import qs.services
+import qs.modules.common
+import qs.modules.common.functions
+import qs.modules.waffle.looks
+import Quickshell
+
+PopupWindow {
+ id: root
+
+ ///////////////////// Properties ////////////////////
+ required property bool tasksHovered
+ property var appEntry
+ property Item anchorItem
+
+ //////////////////// Functions ////////////////////
+ function close() {
+ marginBehavior.enabled = false;
+ root.visible = false;
+ }
+
+ function open() {
+ marginBehavior.enabled = true;
+ root.visible = true;
+ }
+
+ function show(appEntry: var, button: Item) {
+ root.appEntry = appEntry;
+ root.anchorItem = button;
+ root.anchor.updateAnchor();
+ root.open();
+ }
+
+ ///////////////////// Internals /////////////////////
+ readonly property bool bottom: Config.options.waffles.bar.bottom
+ property real visualMargin: 12
+ property alias ambientShadowWidth: ambientShadow.border.width
+
+ visible: false
+ color: "transparent"
+ implicitWidth: contentItem.implicitWidth + ambientShadowWidth + (visualMargin * 2)
+ implicitHeight: contentItem.implicitHeight + ambientShadowWidth + (visualMargin * 2)
+ anchor {
+ adjustment: PopupAdjustment.Slide
+ item: root.anchorItem
+ gravity: bottom ? Edges.Top : Edges.Bottom
+ edges: bottom ? Edges.Top : Edges.Bottom
+ }
+
+ Timer {
+ interval: 250
+ running: root.visible && !hoverChecker.containsMouse && !root.tasksHovered
+ onTriggered: {
+ root.close();
+ }
+ }
+
+ // Content
+ MouseArea {
+ id: hoverChecker
+ anchors.fill: parent
+ hoverEnabled: true
+
+ // Shadow
+ Rectangle {
+ id: ambientShadow
+ anchors {
+ fill: contentItem
+ margins: -border.width
+ }
+ border.color: ColorUtils.transparentize(Looks.colors.bg0Border, Looks.contentTransparency)
+ border.width: 1
+ color: "transparent"
+ radius: Looks.radius.large + border.width
+ }
+
+ Rectangle {
+ id: contentItem
+ property real sourceEdgeMargin: root.visible ? (root.ambientShadowWidth + root.visualMargin) : -root.implicitHeight
+ Behavior on sourceEdgeMargin {
+ id: marginBehavior
+ animation: Looks.transition.enter.createObject(this)
+ }
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: root.bottom ? undefined : parent.top
+ bottom: root.bottom ? parent.bottom : undefined
+ margins: root.ambientShadowWidth + root.visualMargin
+ // Opening anim
+ bottomMargin: root.bottom ? sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)
+ topMargin: root.bottom ? (root.ambientShadowWidth + root.visualMargin) : sourceEdgeMargin
+ }
+ color: Looks.colors.bg1
+ radius: Looks.radius.large
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ width: contentItem.width
+ height: contentItem.height
+ radius: contentItem.radius
+ }
+ }
+
+ // Testing
+ implicitHeight: Math.min(158, windowsRow.implicitHeight)
+ implicitWidth: windowsRow.implicitWidth
+
+ RowLayout {
+ id: windowsRow
+ anchors.fill: parent
+
+ Repeater {
+ model: ScriptModel {
+ values: root.appEntry?.toplevels ?? []
+ }
+ delegate: WindowPreview {
+ required property var modelData
+ toplevel: modelData
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/Tasks.qml b/dots/.config/quickshell/ii/modules/waffle/bar/Tasks.qml
index ba8944999..eac963722 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/Tasks.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/Tasks.qml
@@ -5,30 +5,41 @@ import qs.services
import qs.modules.common
import qs.modules.waffle.looks
-Item {
+MouseArea {
id: root
Layout.fillHeight: true
implicitHeight: row.implicitHeight
implicitWidth: row.implicitWidth
+ hoverEnabled: true
// Apps row
RowLayout {
id: row
anchors.fill: parent
- spacing: 4
+ spacing: 0
Repeater {
+ // TODO: Include only apps (and windows) in current workspace only
model: ScriptModel {
objectProp: "appId"
values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR")
}
delegate: TaskAppButton {
required property var modelData
- toplevel: modelData
+ appEntry: modelData
+
+ onHoverPreviewRequested: {
+ previewPopup.show(appEntry, this)
+ }
}
}
}
- // TODO: Previews popup
+ // Previews popup
+ TaskPreview {
+ id: previewPopup
+ tasksHovered: root.containsMouse
+ anchor.window: root.QsWindow.window
+ }
}
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml
index 1a9763616..5228abb5c 100644
--- a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml
@@ -36,7 +36,6 @@ Rectangle {
BarGroupRow {
id: appsRow
- spacing: 4
anchors.left: undefined
anchors.horizontalCenter: parent.horizontalCenter
diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/WindowPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/WindowPreview.qml
new file mode 100644
index 000000000..b1944c350
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/waffle/bar/WindowPreview.qml
@@ -0,0 +1,135 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt5Compat.GraphicalEffects
+import qs.services
+import qs.modules.common
+import qs.modules.common.functions
+import qs.modules.waffle.looks
+import Quickshell
+import Quickshell.Wayland
+
+Button {
+ id: root
+
+ required property var toplevel
+ property real previewWidthConstraint: 200
+ property real previewHeightConstraint: 110
+ padding: 5
+ Layout.fillHeight: true
+
+ onClicked: {
+ root.toplevel.activate(); // TODO: make this work with those who disable focus on activate because telegram is abusive
+ }
+
+ background: Rectangle {
+ id: background
+ radius: Looks.radius.medium
+ color: root.down ? Looks.colors.bg2Active : (root.hovered ? Looks.colors.bg2Hover : ColorUtils.transparentize(Looks.colors.bg2))
+ Behavior on color {
+ animation: Looks.transition.color.createObject(this)
+ }
+ }
+
+ contentItem: ColumnLayout {
+ id: contentItem
+ anchors.fill: parent
+ anchors.margins: root.padding
+ spacing: 5
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: false
+ spacing: 8
+
+ AppIcon {
+ id: appIcon
+ Layout.leftMargin: Looks.radius.large - root.padding + 2
+ Layout.alignment: Qt.AlignVCenter
+ iconName: AppSearch.guessIcon(root.toplevel.appId)
+ implicitSize: 16
+ }
+
+ Item {
+ id: appTitleContainer
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ implicitHeight: closeButton.implicitHeight // Enforce height, because closeButton doesn't contribute when it's invisible
+ WText {
+ id: appTitleText
+ anchors.fill: parent
+ text: root.toplevel.title
+ elide: Text.ElideRight
+ font.pixelSize: Looks.font.pixelSize.large
+ font.weight: Looks.font.weight.thin
+ color: Looks.colors.fg1
+ }
+ }
+
+ CloseButton {
+ id: closeButton
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: Looks.radius.large - root.padding
+ Layout.topMargin: 0
+ implicitWidth: Math.max(screencopyView.implicitWidth, 80)
+ implicitHeight: screencopyView.implicitHeight
+
+ ScreencopyView {
+ id: screencopyView
+ anchors.centerIn: parent
+ captureSource: root.toplevel
+ live: true
+ paintCursor: true
+ constraintSize: Qt.size(root.previewWidthConstraint, root.previewHeightConstraint)
+ }
+ }
+ }
+
+ component CloseButton: Button {
+ id: reusableCloseButton
+ visible: root.hovered
+ Layout.leftMargin: 4
+ implicitHeight: 30
+ implicitWidth: 30
+ onClicked: {
+ root.toplevel.close();
+ }
+
+ Rectangle {
+ z: 0
+ color: "transparent"
+ anchors.fill: closeButtonBg
+ anchors.margins: -1
+ opacity: closeButtonBg.opacity
+ border.width: 1
+ radius: closeButtonBg.radius + 1
+ border.color: Looks.colors.bg2Border
+ }
+
+ background: Rectangle {
+ id: closeButtonBg
+ z: 1
+ opacity: reusableCloseButton.hovered ? 1 : 0
+ radius: Looks.radius.large - root.padding
+ color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger
+ Behavior on opacity {
+ animation: Looks.transition.opacity.createObject(this)
+ }
+ Behavior on color {
+ animation: Looks.transition.color.createObject(this)
+ }
+ }
+
+ contentItem: FluentIcon {
+ z: 2
+ anchors.centerIn: parent
+ icon: "dismiss"
+ implicitSize: 10
+ }
+ }
+}
diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/AcrylicRectangle.qml b/dots/.config/quickshell/ii/modules/waffle/looks/AcrylicRectangle.qml
new file mode 100644
index 000000000..3720d6186
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/waffle/looks/AcrylicRectangle.qml
@@ -0,0 +1,63 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.modules.common
+import qs.modules.common.functions
+import qs.modules.waffle.looks
+
+Rectangle {
+ id: root
+
+ property bool shiny: true // Top border
+ property color borderColor: ColorUtils.transparentize(Looks.colors.bg1Border, shiny ? Looks.contentTransparency : 1)
+ color: Looks.colors.bg1Hover
+ radius: Looks.radius.medium
+ Behavior on color {
+ animation: Looks.transition.color.createObject(this)
+ }
+ Behavior on borderColor {
+ animation: Looks.transition.color.createObject(this)
+ }
+ onBorderColorChanged: {
+ borderCanvas.requestPaint();
+ }
+
+ // Top 1px border with color
+ Canvas {
+ id: borderCanvas
+ anchors.fill: parent
+ onPaint: {
+ var ctx = getContext("2d");
+ ctx.clearRect(0, 0, width, height);
+
+ var borderColor = root.borderColor;
+
+ var r = root.radius;
+ var fadeLength = Math.max(1, r);
+ var fadeLengthPercent = fadeLength / width;
+
+ // Compute normalized stops
+ var leftFadeStop = fadeLengthPercent;
+ var rightFadeStop = 1 - fadeLengthPercent;
+
+ var grad = ctx.createLinearGradient(0, 0, width, 0);
+ grad.addColorStop(0, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));
+ grad.addColorStop(leftFadeStop, borderColor);
+ grad.addColorStop(rightFadeStop, borderColor);
+ grad.addColorStop(1, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));
+
+ ctx.strokeStyle = grad;
+ ctx.lineWidth = 1;
+
+ ctx.beginPath();
+ ctx.moveTo(r, 0.5);
+ ctx.lineTo(width - r, 0.5);
+ // Top-right curve
+ ctx.arcTo(width, 0.5, width, r + 0.5, r);
+ // Top-left curve
+ ctx.moveTo(width - r, 0.5);
+ ctx.arcTo(0, 0.5, 0, r + 0.5, r);
+ ctx.stroke();
+ }
+ }
+}
diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml
index ea225f093..df7df8187 100644
--- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml
+++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml
@@ -15,17 +15,24 @@ Singleton {
property string iconsPath: `${Directories.assetsPath}/icons/fluent`
property bool dark: Appearance.m3colors.darkmode
- property real fluentBackgroundTransparency: 0.17
- property real fluentContentTransparency: 0.3
+ property real backgroundTransparency: 0.17
+ property real contentTransparency: 0.25
colors: QtObject {
id: colors
property color bg0: root.dark ? "#1C1C1C" : "#EEEEEE"
property color bg0Border: root.dark ? "#404040" : "#BEBEBE"
- property color bg1: root.dark ? "#2E2E2E" : "#F7F7F7"
+ property color bg1: root.dark ? "#2C2C2C" : "#F7F7F7"
property color bg1Hover: root.dark ? "#292929" : "#F7F7F7"
property color bg1Active: root.dark ? "#252525" : "#F3F3F3"
property color bg1Border: root.dark ? "#333333" : "#E9E9E9"
+ property color bg2: root.dark ? "#313131" : "#FBFBFB"
+ property color bg2Hover: root.dark ? "#383838" : "#FDFDFD"
+ property color bg2Active: root.dark ? "#333333" : "#FDFDFD"
+ property color bg2Border: root.dark ? "#464646" : "#EEEEEE"
property color fg: root.dark ? "#FFFFFF" : "#000000"
+ property color fg1: root.dark ? "#D1D1D1" : "#626262"
+ property color danger: "#C42B1C"
+ property color dangerActive: "#B62D1F"
property color brand: Appearance.m3colors.m3primary
}
@@ -44,12 +51,14 @@ Singleton {
property string ui: "Noto Sans"
}
property QtObject weight: QtObject { // Noto is not Segoe, so we might use slightly different weights
+ property int thin: Font.Normal
property int regular: Font.Medium
property int strong: Font.DemiBold
property int stronger: Font.Bold
}
property QtObject pixelSize: QtObject {
property real normal: 11
+ property real large: 15
}
}
@@ -57,15 +66,15 @@ Singleton {
id: transition
property QtObject easing: QtObject {
property QtObject bezierCurve: QtObject {
- readonly property list easeInOut: [0.42,0.00,0.58,1.00]
- readonly property list easeIn: [0,1,1,1]
- readonly property list easeOut: [1,0,1,1]
+ readonly property list easeInOut: [0.42,0.00,0.58,1.00,1,1]
+ readonly property list easeIn: [0,1,1,1,1,1]
+ readonly property list easeOut: [1,0,1,1,1,1]
}
}
property Component color: Component {
ColorAnimation {
- duration: 80
+ duration: 120
easing.type: Easing.BezierSpline
easing.bezierCurve: transition.easing.bezierCurve.easeIn
}
@@ -73,7 +82,7 @@ Singleton {
property Component opacity: Component {
NumberAnimation{
- duration: 80
+ duration: 120
easing.type: Easing.BezierSpline
easing.bezierCurve: transition.easing.bezierCurve.easeIn
}
diff --git a/dots/.config/quickshell/ii/services/AppSearch.qml b/dots/.config/quickshell/ii/services/AppSearch.qml
index 196e1bed3..7d8c375a6 100644
--- a/dots/.config/quickshell/ii/services/AppSearch.qml
+++ b/dots/.config/quickshell/ii/services/AppSearch.qml
@@ -151,6 +151,6 @@ Singleton {
// Give up
- return str;
+ return "application-x-executable";
}
}