mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-25 05:52:50 -05:00
feat: Implement Dank Launcher button on the Dock
- Configurable with custom icons/logos - Respects light/dark theme - Drag & Drop in place
This commit is contained in:
294
quickshell/Modules/Dock/DockLauncherButton.qml
Normal file
294
quickshell/Modules/Dock/DockLauncherButton.qml
Normal file
@@ -0,0 +1,294 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
clip: false
|
||||
property var dockApps: null
|
||||
property int index: -1
|
||||
property bool longPressing: false
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property real dragAxisOffset: 0
|
||||
property int targetIndex: -1
|
||||
property int originalIndex: -1
|
||||
property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||
property real actualIconSize: 40
|
||||
|
||||
readonly property string tooltipText: I18n.tr("Applications")
|
||||
|
||||
readonly property color effectiveLogoColor: {
|
||||
const override = SettingsData.dockLauncherLogoColorOverride;
|
||||
if (override === "primary")
|
||||
return Theme.primary;
|
||||
if (override === "surface")
|
||||
return Theme.surfaceText;
|
||||
if (override !== "")
|
||||
return override;
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
|
||||
onIsHoveredChanged: {
|
||||
if (mouseArea.pressed || dragging)
|
||||
return;
|
||||
if (isHovered) {
|
||||
exitAnimation.stop();
|
||||
if (!bounceAnimation.running) {
|
||||
bounceAnimation.restart();
|
||||
}
|
||||
} else {
|
||||
bounceAnimation.stop();
|
||||
exitAnimation.restart();
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool animateX: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
|
||||
readonly property real animationDistance: actualIconSize
|
||||
readonly property real animationDirection: {
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Bottom)
|
||||
return -1;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Top)
|
||||
return 1;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Right)
|
||||
return -1;
|
||||
if (SettingsData.dockPosition === SettingsData.Position.Left)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: bounceAnimation
|
||||
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
to: animationDirection * animationDistance * 0.25
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedAccel
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
to: animationDirection * animationDistance * 0.2
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: exitAnimation
|
||||
|
||||
running: false
|
||||
target: root
|
||||
property: "hoverAnimOffset"
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
longPressing = true;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: true
|
||||
preventStealing: true
|
||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressTimer.start();
|
||||
}
|
||||
}
|
||||
onReleased: mouse => {
|
||||
longPressTimer.stop();
|
||||
|
||||
const wasDragging = dragging;
|
||||
const didReorder = wasDragging && targetIndex >= 0 && dockApps;
|
||||
|
||||
if (didReorder) {
|
||||
SessionData.dockLauncherPosition = targetIndex;
|
||||
}
|
||||
|
||||
longPressing = false;
|
||||
dragging = false;
|
||||
dragAxisOffset = 0;
|
||||
targetIndex = -1;
|
||||
originalIndex = -1;
|
||||
|
||||
if (dockApps) {
|
||||
dockApps.draggedIndex = -1;
|
||||
dockApps.dropTargetIndex = -1;
|
||||
}
|
||||
|
||||
if (wasDragging || mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
|
||||
PopoutService.toggleDankLauncherV2();
|
||||
}
|
||||
onPositionChanged: mouse => {
|
||||
if (longPressing && !dragging) {
|
||||
const distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2));
|
||||
if (distance > 5) {
|
||||
dragging = true;
|
||||
targetIndex = index;
|
||||
originalIndex = index;
|
||||
if (dockApps) {
|
||||
dockApps.draggedIndex = index;
|
||||
dockApps.dropTargetIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dragging || !dockApps)
|
||||
return;
|
||||
|
||||
const axisOffset = isVertical ? (mouse.y - dragStartPos.y) : (mouse.x - dragStartPos.x);
|
||||
dragAxisOffset = axisOffset;
|
||||
|
||||
const spacing = Math.min(8, Math.max(4, actualIconSize * 0.08));
|
||||
const itemSize = actualIconSize * 1.2 + spacing;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(dockApps.pinnedAppCount, originalIndex + slotOffset));
|
||||
|
||||
if (newTargetIndex !== targetIndex) {
|
||||
targetIndex = newTargetIndex;
|
||||
dockApps.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property real hoverAnimOffset: 0
|
||||
|
||||
Item {
|
||||
id: visualContent
|
||||
anchors.fill: parent
|
||||
|
||||
transform: Translate {
|
||||
x: dragging && !isVertical ? dragAxisOffset : (!dragging && isVertical ? hoverAnimOffset : 0)
|
||||
y: dragging && isVertical ? dragAxisOffset : (!dragging && !isVertical ? hoverAnimOffset : 0)
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.centerIn: parent
|
||||
width: actualIconSize
|
||||
height: actualIconSize
|
||||
|
||||
DankIcon {
|
||||
visible: SettingsData.dockLauncherLogoMode === "apps"
|
||||
anchors.centerIn: parent
|
||||
name: "apps"
|
||||
size: actualIconSize - 4
|
||||
color: Theme.widgetIconColor
|
||||
}
|
||||
|
||||
SystemLogo {
|
||||
visible: SettingsData.dockLauncherLogoMode === "os"
|
||||
anchors.centerIn: parent
|
||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
colorOverride: effectiveLogoColor
|
||||
brightnessOverride: SettingsData.dockLauncherLogoBrightness
|
||||
contrastOverride: SettingsData.dockLauncherLogoContrast
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.dockLauncherLogoMode === "dank"
|
||||
anchors.centerIn: parent
|
||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
source: "file://" + Theme.shellDir + "/assets/danklogo.svg"
|
||||
layer.enabled: effectiveLogoColor !== ""
|
||||
layer.smooth: true
|
||||
layer.mipmap: true
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: effectiveLogoColor
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.dockLauncherLogoMode === "compositor" && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isLabwc)
|
||||
anchors.centerIn: parent
|
||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: {
|
||||
if (CompositorService.isNiri) {
|
||||
return "file://" + Theme.shellDir + "/assets/niri.svg";
|
||||
} else if (CompositorService.isHyprland) {
|
||||
return "file://" + Theme.shellDir + "/assets/hyprland.svg";
|
||||
} else if (CompositorService.isDwl) {
|
||||
return "file://" + Theme.shellDir + "/assets/mango.png";
|
||||
} else if (CompositorService.isSway) {
|
||||
return "file://" + Theme.shellDir + "/assets/sway.svg";
|
||||
} else if (CompositorService.isScroll) {
|
||||
return "file://" + Theme.shellDir + "/assets/sway.svg";
|
||||
} else if (CompositorService.isLabwc) {
|
||||
return "file://" + Theme.shellDir + "/assets/labwc.png";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
layer.enabled: effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: effectiveLogoColor
|
||||
brightness: {
|
||||
SettingsData.dockLauncherLogoBrightness;
|
||||
}
|
||||
contrast: {
|
||||
SettingsData.dockLauncherLogoContrast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: SettingsData.dockLauncherLogoMode === "custom" && SettingsData.dockLauncherLogoCustomPath !== ""
|
||||
anchors.centerIn: parent
|
||||
width: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
height: actualIconSize + SettingsData.dockLauncherLogoSizeOffset
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
source: SettingsData.dockLauncherLogoCustomPath ? "file://" + SettingsData.dockLauncherLogoCustomPath.replace("file://", "") : ""
|
||||
layer.enabled: effectiveLogoColor !== ""
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: effectiveLogoColor
|
||||
brightness: SettingsData.dockLauncherLogoBrightness
|
||||
contrast: SettingsData.dockLauncherLogoContrast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user