Slight restructure of the Dock so the Variants is in Dock.

This commit is contained in:
quadbyte 2025-08-07 20:26:13 -04:00
parent 4f199c0240
commit 9e78807071
2 changed files with 301 additions and 301 deletions

View file

@ -6,345 +6,356 @@ import Quickshell.Widgets
import qs.Settings import qs.Settings
import qs.Components import qs.Components
PanelWindow {
id: taskbarWindow
visible: Settings.settings.showDock &&
(Settings.settings.dockMonitors.includes(modelData.name) ||
(Settings.settings.dockMonitors.length === 0))
screen: (typeof modelData !== 'undefined' ? modelData : null)
exclusionMode: ExclusionMode.Ignore
anchors.bottom: true
anchors.left: true
anchors.right: true
focusable: false
color: "transparent"
implicitHeight: 43
// Auto-hide properties Variants {
property bool autoHide: true model: Quickshell.screens
property bool hidden: true
property int hideDelay: 500
property int showDelay: 100
property int hideAnimationDuration: 200
property int showAnimationDuration: 150
property int peekHeight: 2
property int fullHeight: taskbarContainer.height
// Track hover state Item {
property bool dockHovered: false property var modelData
property bool anyAppHovered: false
// Context menu properties // Auto-hide properties
property bool contextMenuVisible: false property bool autoHide: true
property var contextMenuTarget: null property bool hidden: true
property var contextMenuToplevel: null property int hideDelay: 500
property int showDelay: 100
property int hideAnimationDuration: 200
property int showAnimationDuration: 150
property int peekHeight: 2
property int fullHeight: dockContainer.height
// Timer for auto-hide delay // Track hover state
Timer { property bool dockHovered: false
id: hideTimer property bool anyAppHovered: false
interval: hideDelay
onTriggered: if (autoHide && !dockHovered && !anyAppHovered && !contextMenuVisible) hidden = true
}
// Timer for show delay // Context menu properties
Timer { property bool contextMenuVisible: false
id: showTimer property var contextMenuTarget: null
interval: showDelay property var contextMenuToplevel: null
onTriggered: hidden = false
}
// Behavior for smooth hide/show animations PanelWindow {
Behavior on margins.bottom {
NumberAnimation {
duration: hidden ? hideAnimationDuration : showAnimationDuration
easing.type: Easing.InOutQuad
}
}
// Mouse area at screen bottom to detect entry and keep dock visible id: dockWindow
MouseArea { visible: Settings.settings.showDock &&
id: screenEdgeMouseArea (Settings.settings.dockMonitors.includes(modelData.name) ||
anchors.left: parent.left (Settings.settings.dockMonitors.length === 0))
anchors.right: parent.right screen: modelData
anchors.bottom: parent.bottom exclusionMode: ExclusionMode.Ignore
height: 10 anchors.bottom: true
hoverEnabled: true anchors.left: true
propagateComposedEvents: true anchors.right: true
focusable: false
color: "transparent"
implicitHeight: 43
onEntered: if (autoHide && hidden) showTimer.start() // Timer for auto-hide delay
onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered && !contextMenuVisible) hideTimer.start() Timer {
} id: hideTimer
interval: hideDelay
onTriggered: if (autoHide && !dockHovered && !anyAppHovered && !contextMenuVisible) hidden = true
}
margins.bottom: hidden ? -(fullHeight - peekHeight) : 0 // Timer for show delay
Timer {
id: showTimer
interval: showDelay
onTriggered: hidden = false
}
Rectangle { // Behavior for smooth hide/show animations
id: taskbarContainer Behavior on margins.bottom {
width: taskbar.width + 40 NumberAnimation {
height: Settings.settings.taskbarIconSize + 20 duration: hidden ? hideAnimationDuration : showAnimationDuration
topLeftRadius: 16 easing.type: Easing.InOutQuad
topRightRadius: 16
color: Theme.backgroundSecondary
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
MouseArea {
id: dockMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onEntered: {
dockHovered = true
if (autoHide) {
showTimer.stop()
hideTimer.stop()
hidden = false
} }
} }
onExited: {
dockHovered = false
if (autoHide && !anyAppHovered && !contextMenuVisible) hideTimer.start()
}
}
Item { // Mouse area at screen bottom to detect entry and keep dock visible
id: taskbar MouseArea {
width: runningAppsRow.width id: screenEdgeMouseArea
height: parent.height - 10 anchors.left: parent.left
anchors.centerIn: parent anchors.right: parent.right
anchors.bottom: parent.bottom
height: 10
hoverEnabled: true
propagateComposedEvents: true
StyledTooltip { id: styledTooltip } onEntered: if (autoHide && hidden) showTimer.start()
onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered && !contextMenuVisible) hideTimer.start()
function getAppIcon(toplevel: Toplevel): string {
if (!toplevel) return "";
let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true);
if (!icon) icon = Quickshell.iconPath(toplevel.appId, true);
if (!icon) icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true);
if (!icon) icon = Quickshell.iconPath(toplevel.title, true);
return icon || Quickshell.iconPath("application-x-executable", true);
} }
Row { margins.bottom: hidden ? -(fullHeight - peekHeight) : 0
id: runningAppsRow
spacing: 12
height: parent.height
anchors.centerIn: parent
Repeater { Rectangle {
model: ToplevelManager ? ToplevelManager.toplevels : null id: dockContainer
width: dock.width + 40
height: Settings.settings.taskbarIconSize + 20
topLeftRadius: 16
topRightRadius: 16
color: Theme.backgroundSecondary
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
delegate: Rectangle { MouseArea {
id: appButton id: dockMouseArea
width: Settings.settings.taskbarIconSize + 8 anchors.fill: parent
height: Settings.settings.taskbarIconSize + 8 hoverEnabled: true
radius: Math.max(6, Settings.settings.taskbarIconSize * 0.3) propagateComposedEvents: true
color: isActive ? Theme.accentPrimary : (hovered ? Theme.surfaceVariant : "transparent")
border.color: isActive ? Qt.darker(Theme.accentPrimary, 1.2) : "transparent"
border.width: 1
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData onEntered: {
property bool hovered: appMouseArea.containsMouse dockHovered = true
property string appId: modelData ? modelData.appId : "" if (autoHide) {
property string appTitle: modelData ? modelData.title : "" showTimer.stop()
hideTimer.stop()
Behavior on color { ColorAnimation { duration: 150 } } hidden = false
Behavior on border.color { ColorAnimation { duration: 150 } }
IconImage {
id: appIcon
width: Math.max(20, Settings.settings.taskbarIconSize * 0.75)
height: Math.max(20, Settings.settings.taskbarIconSize * 0.75)
anchors.centerIn: parent
source: taskbar.getAppIcon(modelData)
visible: source.toString() !== ""
} }
}
onExited: {
dockHovered = false
if (autoHide && !anyAppHovered && !contextMenuVisible) hideTimer.start()
}
}
Text { Item {
anchors.centerIn: parent id: dock
visible: !appIcon.visible width: runningAppsRow.width
text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?" height: parent.height - 10
font.family: Theme.fontFamily anchors.centerIn: parent
font.pixelSize: Math.max(14, Settings.settings.taskbarIconSize * 0.5)
font.bold: true
color: appButton.isActive ? Theme.onAccent : Theme.textPrimary
}
MouseArea { StyledTooltip { id: styledTooltip }
id: appMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: { function getAppIcon(toplevel: Toplevel): string {
anyAppHovered = true if (!toplevel) return "";
if (!contextMenuVisible) { let icon = Quickshell.iconPath(toplevel.appId?.toLowerCase(), true);
styledTooltip.text = appTitle || appId; if (!icon) icon = Quickshell.iconPath(toplevel.appId, true);
styledTooltip.targetItem = appButton; if (!icon) icon = Quickshell.iconPath(toplevel.title?.toLowerCase(), true);
styledTooltip.positionAbove = true; if (!icon) icon = Quickshell.iconPath(toplevel.title, true);
styledTooltip.tooltipVisible = true; return icon || Quickshell.iconPath("application-x-executable", true);
}
Row {
id: runningAppsRow
spacing: 12
height: parent.height
anchors.centerIn: parent
Repeater {
model: ToplevelManager ? ToplevelManager.toplevels : null
delegate: Rectangle {
id: appButton
width: Settings.settings.taskbarIconSize + 8
height: Settings.settings.taskbarIconSize + 8
radius: Math.max(6, Settings.settings.taskbarIconSize * 0.3)
color: isActive ? Theme.accentPrimary : (hovered ? Theme.surfaceVariant : "transparent")
border.color: isActive ? Qt.darker(Theme.accentPrimary, 1.2) : "transparent"
border.width: 1
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
property bool hovered: appMouseArea.containsMouse
property string appId: modelData ? modelData.appId : ""
property string appTitle: modelData ? modelData.title : ""
Behavior on color { ColorAnimation { duration: 150 } }
Behavior on border.color { ColorAnimation { duration: 150 } }
IconImage {
id: appIcon
width: Math.max(20, Settings.settings.taskbarIconSize * 0.75)
height: Math.max(20, Settings.settings.taskbarIconSize * 0.75)
anchors.centerIn: parent
source: dock.getAppIcon(modelData)
visible: source.toString() !== ""
} }
if (autoHide) {
showTimer.stop() Text {
hideTimer.stop() anchors.centerIn: parent
hidden = false visible: !appIcon.visible
text: appButton.appId ? appButton.appId.charAt(0).toUpperCase() : "?"
font.family: Theme.fontFamily
font.pixelSize: Math.max(14, Settings.settings.taskbarIconSize * 0.5)
font.bold: true
color: appButton.isActive ? Theme.onAccent : Theme.textPrimary
}
MouseArea {
id: appMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: {
anyAppHovered = true
if (!contextMenuVisible) {
styledTooltip.text = appTitle || appId;
styledTooltip.targetItem = appButton;
styledTooltip.positionAbove = true;
styledTooltip.tooltipVisible = true;
}
if (autoHide) {
showTimer.stop()
hideTimer.stop()
hidden = false
}
}
onExited: {
anyAppHovered = false
if (!contextMenuVisible) {
styledTooltip.tooltipVisible = false;
}
if (autoHide && !dockHovered && !contextMenuVisible) hideTimer.start()
}
onClicked: function(mouse) {
if (mouse.button === Qt.MiddleButton && modelData?.close) {
modelData.close();
}
if (mouse.button === Qt.LeftButton && modelData?.activate) {
modelData.activate();
}
if (mouse.button === Qt.RightButton) {
styledTooltip.tooltipVisible = false;
contextMenuTarget = appButton;
contextMenuToplevel = modelData;
contextMenuVisible = true;
}
}
}
Rectangle {
visible: isActive
width: 6
height: 6
radius: 3
color: Theme.onAccent
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: -8
} }
} }
onExited: {
anyAppHovered = false
if (!contextMenuVisible) {
styledTooltip.tooltipVisible = false;
}
if (autoHide && !dockHovered && !contextMenuVisible) hideTimer.start()
}
onClicked: function(mouse) {
if (mouse.button === Qt.MiddleButton && modelData?.close) {
modelData.close();
}
if (mouse.button === Qt.LeftButton && modelData?.activate) {
modelData.activate();
}
if (mouse.button === Qt.RightButton) {
styledTooltip.tooltipVisible = false;
contextMenuTarget = appButton;
contextMenuToplevel = modelData;
contextMenuVisible = true;
}
}
}
Rectangle {
visible: isActive
width: 6
height: 6
radius: 3
color: Theme.onAccent
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: -8
} }
} }
} }
} }
}
}
// Context Menu // Context Menu
PanelWindow { PanelWindow {
id: contextMenuWindow id: contextMenuWindow
visible: contextMenuVisible visible: contextMenuVisible
screen: taskbarWindow.screen screen: dockWindow.screen
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore
anchors.bottom: true anchors.bottom: true
anchors.left: true anchors.left: true
anchors.right: true anchors.right: true
color: "transparent" color: "transparent"
focusable: false focusable: false
MouseArea {
anchors.fill: parent
onClicked: {
contextMenuVisible = false;
contextMenuTarget = null;
contextMenuToplevel = null;
hidden = true; // Hide dock when context menu closes
}
}
Rectangle {
id: contextMenuContainer
width: 80
height: contextMenuColumn.height + 0
radius: 16
color: Theme.backgroundPrimary
border.color: Theme.outline
border.width: 1
x: {
if (!contextMenuTarget) return 0;
// Get position relative to screen
const pos = contextMenuTarget.mapToItem(null, 0, 0);
// Center horizontally above the icon
let xPos = pos.x + (contextMenuTarget.width - width) / 2;
// Constrain to screen edges
return Math.max(0, Math.min(xPos, taskbarWindow.width - width));
}
y: {
if (!contextMenuTarget) return 0;
// Position above the dock
const pos = contextMenuTarget.mapToItem(null, 0, 0);
return pos.y - height + 32;
}
Column {
id: contextMenuColumn
anchors.centerIn: parent
spacing: 4
width: parent.width
MouseArea {
anchors.fill: parent
onClicked: {
contextMenuVisible = false;
contextMenuTarget = null;
contextMenuToplevel = null;
hidden = true; // Hide dock when context menu closes
}
}
Rectangle { Rectangle {
width: parent.width id: contextMenuContainer
height: 32 width: 80
height: contextMenuColumn.height + 0
radius: 16 radius: 16
color: closeMouseArea.containsMouse ? Theme.surfaceVariant : "transparent" color: Theme.backgroundPrimary
border.color: Theme.outline border.color: Theme.outline
border.width: 1 border.width: 1
Row { x: {
anchors.left: parent.left if (!contextMenuTarget) return 0;
anchors.leftMargin: 12 // Get position relative to screen
anchors.verticalCenter: parent.verticalCenter const pos = contextMenuTarget.mapToItem(null, 0, 0);
// Center horizontally above the icon
let xPos = pos.x + (contextMenuTarget.width - width) / 2;
// Constrain to screen edges
return Math.max(0, Math.min(xPos, dockWindow.width - width));
}
y: {
if (!contextMenuTarget) return 0;
// Position above the dock
const pos = contextMenuTarget.mapToItem(null, 0, 0);
return pos.y - height + 32;
}
Column {
id: contextMenuColumn
anchors.centerIn: parent
spacing: 4 spacing: 4
width: parent.width
Text {
anchors.verticalCenter: parent.verticalCenter
text: "close"
font.family: "Material Symbols Outlined"
font.pixelSize: 14 * Theme.scale(Screen)
color: Theme.textPrimary
}
Text { Rectangle {
anchors.verticalCenter: parent.verticalCenter width: parent.width
text: "Close" height: 32
font.family: Theme.fontFamily radius: 16
font.pixelSize: 14 * Theme.scale(Screen) color: closeMouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
color: Theme.textPrimary border.color: Theme.outline
border.width: 1
Row {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Text {
anchors.verticalCenter: parent.verticalCenter
text: "close"
font.family: "Material Symbols Outlined"
font.pixelSize: 14 * Theme.scale(Screen)
color: Theme.textPrimary
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Close"
font.family: Theme.fontFamily
font.pixelSize: 14 * Theme.scale(Screen)
color: Theme.textPrimary
}
}
MouseArea {
id: closeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenuToplevel?.close) contextMenuToplevel.close();
contextMenuVisible = false;
hidden = true;
}
}
} }
} }
MouseArea { // Animation
id: closeMouseArea scale: contextMenuVisible ? 1 : 0.9
anchors.fill: parent opacity: contextMenuVisible ? 1 : 0
hoverEnabled: true transformOrigin: Item.Bottom
cursorShape: Qt.PointingHandCursor
onClicked: { Behavior on scale {
if (contextMenuToplevel?.close) contextMenuToplevel.close(); NumberAnimation {
contextMenuVisible = false; duration: 150
hidden = true; easing.type: Easing.OutBack
} }
} }
Behavior on opacity {
NumberAnimation { duration: 100 }
}
} }
} }
// Animation
scale: contextMenuVisible ? 1 : 0.9
opacity: contextMenuVisible ? 1 : 0
transformOrigin: Item.Bottom
Behavior on scale {
NumberAnimation {
duration: 150
easing.type: Easing.OutBack
}
}
Behavior on opacity {
NumberAnimation { duration: 100 }
}
} }
} }
} }

View file

@ -16,7 +16,6 @@ import qs.Helpers
Scope { Scope {
id: root id: root
property alias appLauncherPanel: appLauncherPanel
property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null
property var settingsWindow: null property var settingsWindow: null
property bool pendingReload: false property bool pendingReload: false
@ -59,19 +58,9 @@ Scope {
Bar { Bar {
id: bar id: bar
shell: root
property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null
} }
Variants {
model: Quickshell.screens
Dock {
id: dock
property var modelData
}
}
Dock { Dock {
id: dock id: dock
} }