Add dock & setting for it, edit StyledTooltip, move settings tabs around

This commit is contained in:
Ly-sec 2025-08-02 15:53:19 +02:00
parent 74b233798d
commit f493fdea44
13 changed files with 493 additions and 53 deletions

View file

@ -73,6 +73,7 @@ Item {
}
StyledTooltip {
id: batteryTooltip
positionAbove: false
text: {
let lines = [];
if (batteryWidget.isReady) {

View file

@ -128,6 +128,7 @@ Item {
StyledTooltip {
id: brightnessTooltip
text: "Brightness: " + brightness + "%"
positionAbove: false
tooltipVisible: false
targetItem: pill
delay: 1500

View file

@ -41,6 +41,7 @@ Rectangle {
StyledTooltip {
id: dateTooltip
text: Time.dateString
positionAbove: false
tooltipVisible: showTooltip && !calendar.visible
targetItem: clockWidget
delay: 200

View file

@ -138,6 +138,7 @@ Row {
StyledTooltip {
id: trayTooltip
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
positionAbove: false
tooltipVisible: false
targetItem: trayIcon
delay: 200

View file

@ -48,6 +48,7 @@ Item {
StyledTooltip {
id: volumeTooltip
text: "Volume: " + volume + "%\nScroll up/down to change volume.\nLeft click to open the input/output selection."
positionAbove: false
tooltipVisible: !ioSelector.visible && volumeDisplay.containsMouse
targetItem: pillIndicator
delay: 1500

View file

@ -8,6 +8,10 @@ Window {
property bool tooltipVisible: false
property Item targetItem: null
property int delay: 300
// New property to control positioning: true => above, false => below
property bool positionAbove: true
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
color: "transparent"
visible: false
@ -15,11 +19,14 @@ Window {
minimumWidth: tooltipText.implicitWidth + 24
minimumHeight: tooltipText.implicitHeight + 16
property var _timerObj: null
onTooltipVisibleChanged: {
if (tooltipVisible) {
if (delay > 0) {
if (_timerObj) { _timerObj.destroy(); _timerObj = null; }
_timerObj = Qt.createQmlObject('import QtQuick 2.0; Timer { interval: ' + delay + '; running: true; repeat: false; onTriggered: tooltipWindow._showNow() }', tooltipWindow);
_timerObj = Qt.createQmlObject(
'import QtQuick 2.0; Timer { interval: ' + delay + '; running: true; repeat: false; onTriggered: tooltipWindow._showNow() }',
tooltipWindow);
} else {
_showNow();
}
@ -27,30 +34,42 @@ Window {
_hideNow();
}
}
function _showNow() {
if (!targetItem) return;
var pos = targetItem.mapToGlobal(0, targetItem.height);
x = pos.x - width / 2 + targetItem.width / 2;
y = pos.y + 12;
if (positionAbove) {
// Position tooltip above the target item
var pos = targetItem.mapToGlobal(0, 0);
x = pos.x - width / 2 + targetItem.width / 2;
y = pos.y - height - 12; // 12 px margin above
} else {
// Position tooltip below the target item
var pos = targetItem.mapToGlobal(0, targetItem.height);
x = pos.x - width / 2 + targetItem.width / 2;
y = pos.y + 12; // 12 px margin below
}
visible = true;
}
function _hideNow() {
visible = false;
if (_timerObj) { _timerObj.destroy(); _timerObj = null; }
}
Connections {
target: tooltipWindow.targetItem
function onXChanged() {
if (tooltipWindow.visible) tooltipWindow._showNow()
if (tooltipWindow.visible) tooltipWindow._showNow();
}
function onYChanged() {
if (tooltipWindow.visible) tooltipWindow._showNow()
if (tooltipWindow.visible) tooltipWindow._showNow();
}
function onWidthChanged() {
if (tooltipWindow.visible) tooltipWindow._showNow()
if (tooltipWindow.visible) tooltipWindow._showNow();
}
function onHeightChanged() {
if (tooltipWindow.visible) tooltipWindow._showNow()
if (tooltipWindow.visible) tooltipWindow._showNow();
}
}
@ -63,6 +82,7 @@ Window {
opacity: 0.97
z: 1
}
Text {
id: tooltipText
text: tooltipWindow.text
@ -76,15 +96,16 @@ Window {
padding: 8
z: 2
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onExited: tooltipWindow.tooltipVisible = false
cursorShape: Qt.ArrowCursor
}
onTextChanged: {
width = Math.max(minimumWidth, tooltipText.implicitWidth + 24);
height = Math.max(minimumHeight, tooltipText.implicitHeight + 16);
}
}
}

View file

@ -65,6 +65,9 @@ Singleton {
property real fontSizeMultiplier: 1.0 // Font size multiplier (1.0 = normal, 1.2 = 20% larger, 0.8 = 20% smaller)
property int taskbarIconSize: 24 // Taskbar icon button size in pixels (default: 32, smaller: 24, larger: 40)
property var pinnedExecs: [] // Added for AppLauncher pinned apps
property bool showDock: true
property bool dockExclusive: false
}
}

349
Widgets/Dock.qml Normal file
View file

@ -0,0 +1,349 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Settings
import qs.Components
PanelWindow {
id: taskbarWindow
visible: Settings.settings.showDock
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
property bool autoHide: true
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
property bool dockHovered: false
property bool anyAppHovered: false
// Context menu properties
property bool contextMenuVisible: false
property var contextMenuTarget: null
property var contextMenuToplevel: null
// Timer for auto-hide delay
Timer {
id: hideTimer
interval: hideDelay
onTriggered: if (autoHide && !dockHovered && !anyAppHovered && !contextMenuVisible) hidden = true
}
// Timer for show delay
Timer {
id: showTimer
interval: showDelay
onTriggered: hidden = false
}
// Behavior for smooth hide/show animations
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
MouseArea {
id: screenEdgeMouseArea
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 10
hoverEnabled: true
propagateComposedEvents: true
onEntered: if (autoHide && hidden) showTimer.start()
onExited: if (autoHide && !hidden && !dockHovered && !anyAppHovered && !contextMenuVisible) hideTimer.start()
}
margins.bottom: hidden ? -(fullHeight - peekHeight) : 0
Rectangle {
id: taskbarContainer
width: taskbar.width + 40
height: Settings.settings.taskbarIconSize + 20
topLeftRadius: 16
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 {
id: taskbar
width: runningAppsRow.width
height: parent.height - 10
anchors.centerIn: parent
StyledTooltip { id: styledTooltip }
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 {
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: taskbar.getAppIcon(modelData)
smooth: true
visible: source.toString() !== ""
}
Text {
anchors.centerIn: parent
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
}
}
}
}
}
}
// Context Menu
PanelWindow {
id: contextMenuWindow
visible: contextMenuVisible
screen: taskbarWindow.screen
exclusionMode: ExclusionMode.Ignore
anchors.bottom: true
anchors.left: true
anchors.right: true
color: "transparent"
focusable: false
MouseArea {
anchors.fill: parent
onClicked: {
contextMenuVisible = false;
contextMenuTarget = null;
contextMenuToplevel = null;
hidden = true; // Hide dock when context menu closes by clicking outside
}
}
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
// Close
Rectangle {
width: parent.width
height: 32
radius: 16
color: closeMouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
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
color: Theme.textPrimary
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Close"
font.family: Theme.fontFamily
font.pixelSize: 14
color: Theme.textPrimary
}
}
MouseArea {
id: closeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenuToplevel?.close) contextMenuToplevel.close();
contextMenuVisible = false;
hidden = true; // Hide the dock here as well
}
}
}
}
// 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

@ -55,6 +55,7 @@ Item {
StyledTooltip {
id: notificationTooltip
text: "Notification History"
positionAbove: false
tooltipVisible: false
targetItem: bell
delay: 200

View file

@ -7,7 +7,7 @@ import qs.Settings
Rectangle {
id: profileSettingsCard
Layout.fillWidth: true
Layout.preferredHeight: 650
Layout.preferredHeight: 690
color: Theme.surface
radius: 18
@ -353,6 +353,61 @@ Rectangle {
}
}
// Show Dock Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Dock"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: dockSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showDock ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showDock ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: dockThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showDock ? taskbarSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showDock = !Settings.settings.showDock
}
}
}
}
// Show Media In Bar Setting
RowLayout {
spacing: 8

View file

@ -31,7 +31,6 @@ PanelWindow {
anchors.leftMargin: 32
anchors.rightMargin: 32
anchors.topMargin: 32
spacing: 24
// Header
@ -85,14 +84,14 @@ PanelWindow {
}
}
// Tabs bar (moved here)
// Tabs bar (reordered)
Tabs {
id: settingsTabs
Layout.fillWidth: true
tabsModel: [
{ icon: "cloud", label: "Weather" },
{ icon: "settings", label: "System" },
{ icon: "wallpaper", label: "Wallpaper" }
{ icon: "wallpaper", label: "Wallpaper" },
{ icon: "cloud", label: "Weather" }
]
}
@ -115,7 +114,32 @@ PanelWindow {
id: tabContentLoader
anchors.top: parent.top
width: parent.width
sourceComponent: settingsTabs.currentIndex === 0 ? weatherTab : settingsTabs.currentIndex === 1 ? systemTab : wallpaperTab
sourceComponent: settingsTabs.currentIndex === 0 ? systemTab : settingsTabs.currentIndex === 1 ? wallpaperTab : weatherTab
}
}
Component {
id: systemTab
ColumnLayout {
anchors.fill: parent
ProfileSettings {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
Component {
id: wallpaperTab
ColumnLayout {
anchors.fill: parent
WallpaperSettings {
id: wallpaperSettings
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
@ -130,29 +154,6 @@ PanelWindow {
}
}
}
Component {
id: systemTab
ColumnLayout {
anchors.fill: parent
ProfileSettings {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
Component {
id: wallpaperTab
ColumnLayout {
anchors.fill: parent
WallpaperSettings {
id: wallpaperSettings
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
}
}
}
@ -160,7 +161,6 @@ PanelWindow {
// Function to open the modal and initialize temp values
function openSettings() {
visible = true;
// Force focus on the text input after a short delay
focusTimer.start();
}
@ -174,20 +174,16 @@ PanelWindow {
interval: 100
repeat: false
onTriggered: {
if (visible)
// Focus will be handled by the individual components
{}
}
}
// Release focus when modal becomes invisible
onVisibleChanged: {
if (!visible) {
// Focus will be handled by the individual components
if (typeof weather !== 'undefined' && weather !== null && weather.fetchCityWeather) {
weather.fetchCityWeather();
if (visible) {
// Focus logic can go here if needed
}
}
}
}
// Refresh weather data when hidden
onVisibleChanged: {
if (!visible && typeof weather !== 'undefined' && weather !== null && weather.fetchCityWeather) {
weather.fetchCityWeather();
}
}
}

View file

@ -422,6 +422,12 @@ Rectangle {
running: false
}
Process {
id: logoutProcess
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
running: false
}
function logout() {
if (WorkspaceManager.isNiri) {
logoutProcessNiri.running = true;

View file

@ -53,6 +53,10 @@ Scope {
property var notificationHistoryWin: notificationHistoryWin
}
Dock {
id: dock
}
Applauncher {
id: appLauncherPanel
visible: false