feat: init quickshell
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Dual-button notification and clipboard history toggle bar
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 42
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 12
|
||||
z: 2 // Above notification history overlay
|
||||
|
||||
required property bool notificationHistoryVisible
|
||||
required property bool clipboardHistoryVisible
|
||||
required property var notificationHistory
|
||||
signal notificationToggleRequested
|
||||
signal clipboardToggleRequested
|
||||
|
||||
// Combined hover state for parent component tracking
|
||||
property bool containsMouse: notifButtonMouseArea.containsMouse || clipButtonMouseArea.containsMouse
|
||||
|
||||
property real buttonHeight: 38
|
||||
height: buttonHeight * 2 + 4 // Two buttons with spacing
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
|
||||
// Notifications toggle button (top half)
|
||||
Rectangle {
|
||||
id: notificationPill
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.verticalCenter
|
||||
bottomMargin: 2 // Half of button spacing
|
||||
}
|
||||
radius: 12
|
||||
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
|
||||
border.color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
id: notifButtonMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.notificationToggleRequested()
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "notifications"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
}
|
||||
}
|
||||
|
||||
// Clipboard toggle button (bottom half)
|
||||
Rectangle {
|
||||
id: clipboardPill
|
||||
anchors {
|
||||
top: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
topMargin: 2 // Half of button spacing
|
||||
}
|
||||
radius: 12
|
||||
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
|
||||
border.color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
|
||||
border.width: 1
|
||||
|
||||
MouseArea {
|
||||
id: clipButtonMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.clipboardToggleRequested()
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "content_paste"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import QtQuick
|
||||
|
||||
// Top-edge hover trigger
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 360
|
||||
height: 1
|
||||
color: "red"
|
||||
anchors.top: parent.top
|
||||
|
||||
signal triggered
|
||||
|
||||
// Hover detection area at screen top edge
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
property bool isHovered: containsMouse
|
||||
|
||||
// Timer coordination
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
showTimer.start();
|
||||
hideTimer.stop();
|
||||
} else {
|
||||
hideTimer.start();
|
||||
showTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: hideTimer.stop()
|
||||
}
|
||||
|
||||
// Delayed show trigger to prevent accidental activation
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: 200
|
||||
onTriggered: root.triggered()
|
||||
}
|
||||
|
||||
// Hide delay timer (controlled by parent)
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 500
|
||||
}
|
||||
|
||||
// Public interface
|
||||
readonly property alias containsMouse: mouseArea.containsMouse
|
||||
function stopHideTimer() {
|
||||
hideTimer.stop();
|
||||
}
|
||||
function startHideTimer() {
|
||||
hideTimer.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "root:/Data" as Data
|
||||
|
||||
// System tray context menu
|
||||
Rectangle {
|
||||
id: root
|
||||
width: parent.width
|
||||
height: visible ? calculatedHeight : 0
|
||||
visible: false
|
||||
enabled: visible
|
||||
clip: true
|
||||
color: Data.ThemeManager.bgColor
|
||||
border.color: Data.ThemeManager.accentColor
|
||||
border.width: 2
|
||||
radius: 20
|
||||
|
||||
required property var menu
|
||||
required property var systemTrayY
|
||||
required property var systemTrayHeight
|
||||
|
||||
property bool containsMouse: trayMenuMouseArea.containsMouse
|
||||
property bool menuJustOpened: false
|
||||
property point triggerPoint: Qt.point(0, 0)
|
||||
property Item originalParent
|
||||
property int totalCount: opener.children ? opener.children.values.length : 0
|
||||
|
||||
signal hideRequested
|
||||
|
||||
MouseArea {
|
||||
id: trayMenuMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
propagateComposedEvents: false
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
menuJustOpened = true;
|
||||
Qt.callLater(function () {
|
||||
menuJustOpened = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
visible = !visible;
|
||||
if (visible) {
|
||||
menuJustOpened = true;
|
||||
Qt.callLater(function () {
|
||||
menuJustOpened = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function show(point, parentItem) {
|
||||
visible = true;
|
||||
menuJustOpened = true;
|
||||
Qt.callLater(function () {
|
||||
menuJustOpened = false;
|
||||
});
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false;
|
||||
menuJustOpened = false;
|
||||
// Small delay before notifying hide to prevent control panel flicker
|
||||
Qt.callLater(function () {
|
||||
hideRequested();
|
||||
});
|
||||
}
|
||||
|
||||
// Smart positioning to avoid screen edges
|
||||
y: {
|
||||
var preferredY = systemTrayY + systemTrayHeight + 10;
|
||||
var availableSpace = parent.height - preferredY - 20;
|
||||
if (calculatedHeight > availableSpace) {
|
||||
return systemTrayY - height - 10;
|
||||
}
|
||||
return preferredY;
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic height calculation based on menu item count and types
|
||||
property int calculatedHeight: {
|
||||
if (totalCount === 0)
|
||||
return 40;
|
||||
var separatorCount = 0;
|
||||
var regularItemCount = 0;
|
||||
|
||||
if (opener.children && opener.children.values) {
|
||||
for (var i = 0; i < opener.children.values.length; i++) {
|
||||
if (opener.children.values[i].isSeparator) {
|
||||
separatorCount++;
|
||||
} else {
|
||||
regularItemCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total height: separators + grid rows + margins
|
||||
var separatorHeight = separatorCount * 12;
|
||||
var regularItemRows = Math.ceil(regularItemCount / 2);
|
||||
var regularItemHeight = regularItemRows * 32;
|
||||
return Math.max(80, 35 + separatorHeight + regularItemHeight + 40);
|
||||
}
|
||||
|
||||
// Menu opener handles the native menu integration
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: root.menu
|
||||
}
|
||||
|
||||
// Grid layout for menu items (2 columns)
|
||||
GridView {
|
||||
id: gridView
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
cellWidth: width / 2
|
||||
cellHeight: 32
|
||||
interactive: false
|
||||
flow: GridView.FlowLeftToRight
|
||||
layoutDirection: Qt.LeftToRight
|
||||
|
||||
model: ScriptModel {
|
||||
values: opener.children ? [...opener.children.values] : []
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: entry
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: gridView.cellWidth - 4
|
||||
height: modelData.isSeparator ? 12 : 30
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
anchors.topMargin: 4
|
||||
anchors.bottomMargin: 4
|
||||
visible: modelData.isSeparator
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width * 0.8
|
||||
height: 1
|
||||
color: Qt.darker(Data.ThemeManager.accentColor, 1.5)
|
||||
opacity: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
// Regular menu item
|
||||
Rectangle {
|
||||
id: itemBackground
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
visible: !modelData.isSeparator
|
||||
color: "transparent"
|
||||
radius: 6
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
spacing: 6
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
source: modelData?.icon ?? ""
|
||||
visible: (modelData?.icon ?? "") !== ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: mouseArea.containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
text: modelData?.text ?? ""
|
||||
font.pixelSize: 11
|
||||
font.family: "monospace"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: (modelData?.enabled ?? true) && root.visible && !modelData.isSeparator
|
||||
|
||||
onEntered: itemBackground.color = Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
|
||||
onExited: itemBackground.color = "transparent"
|
||||
onClicked: {
|
||||
modelData.triggered();
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state indicator
|
||||
Item {
|
||||
anchors.centerIn: gridView
|
||||
visible: gridView.count === 0
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "No tray items available"
|
||||
color: Qt.darker(Data.ThemeManager.fgColor, 2)
|
||||
font.pixelSize: 14
|
||||
font.family: "monospace"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user