add crypto
This commit is contained in:
parent
90cbe489f6
commit
af6a3bce3e
120 changed files with 24616 additions and 462 deletions
143
modules/home/services/quickshell/qml/Widgets/Panel/TopPanel.qml
Normal file
143
modules/home/services/quickshell/qml/Widgets/Panel/TopPanel.qml
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "root:/Data" as Data
|
||||
import "root:/Core/" as Core
|
||||
import "./modules" as Modules
|
||||
|
||||
// Top panel wrapper with recording
|
||||
Item {
|
||||
id: topPanelRoot
|
||||
required property var shell
|
||||
|
||||
visible: true
|
||||
|
||||
property bool isRecording: false
|
||||
property var recordingProcess: null
|
||||
property string lastError: ""
|
||||
property bool wallpaperSelectorVisible: false
|
||||
|
||||
signal slideBarVisibilityChanged(bool visible)
|
||||
|
||||
function triggerTopPanel() {
|
||||
panel.show()
|
||||
}
|
||||
|
||||
// Auto-trigger panel
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
triggerTopPanel()
|
||||
}
|
||||
}
|
||||
|
||||
// Main panel instance
|
||||
Modules.Panel {
|
||||
id: panel
|
||||
shell: topPanelRoot.shell
|
||||
isRecording: topPanelRoot.isRecording
|
||||
|
||||
anchors.top: topPanelRoot.top
|
||||
anchors.right: topPanelRoot.right
|
||||
anchors.topMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
|
||||
onVisibleChanged: slideBarVisibilityChanged(visible)
|
||||
|
||||
onRecordingRequested: startRecording()
|
||||
onStopRecordingRequested: {
|
||||
stopRecording()
|
||||
// Hide entire TopPanel after stop recording
|
||||
if (topPanelRoot.parent && topPanelRoot.parent.hide) {
|
||||
topPanelRoot.parent.hide()
|
||||
}
|
||||
}
|
||||
onSystemActionRequested: function(action) {
|
||||
performSystemAction(action)
|
||||
// Hide entire TopPanel after system action
|
||||
if (topPanelRoot.parent && topPanelRoot.parent.hide) {
|
||||
topPanelRoot.parent.hide()
|
||||
}
|
||||
}
|
||||
onPerformanceActionRequested: function(action) {
|
||||
performPerformanceAction(action)
|
||||
// Hide entire TopPanel after performance action
|
||||
if (topPanelRoot.parent && topPanelRoot.parent.hide) {
|
||||
topPanelRoot.parent.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start screen recording
|
||||
function startRecording() {
|
||||
var currentDate = new Date()
|
||||
var hours = String(currentDate.getHours()).padStart(2, '0')
|
||||
var minutes = String(currentDate.getMinutes()).padStart(2, '0')
|
||||
var day = String(currentDate.getDate()).padStart(2, '0')
|
||||
var month = String(currentDate.getMonth() + 1).padStart(2, '0')
|
||||
var year = currentDate.getFullYear()
|
||||
|
||||
var filename = hours + "-" + minutes + "-" + day + "-" + month + "-" + year + ".mp4"
|
||||
var outputPath = Data.Settings.videoPath + filename
|
||||
var command = "gpu-screen-recorder -w portal -f 60 -a default_output -o " + outputPath
|
||||
|
||||
var qmlString = 'import Quickshell.Io; Process { command: ["sh", "-c", "' + command + '"]; running: true }'
|
||||
|
||||
recordingProcess = Qt.createQmlObject(qmlString, topPanelRoot)
|
||||
isRecording = true
|
||||
}
|
||||
|
||||
// Stop recording with cleanup
|
||||
function stopRecording() {
|
||||
if (recordingProcess && isRecording) {
|
||||
var stopQmlString = 'import Quickshell.Io; Process { command: ["sh", "-c", "pkill -SIGINT -f \'gpu-screen-recorder.*portal\'"]; running: true; onExited: function() { destroy() } }'
|
||||
|
||||
var stopProcess = Qt.createQmlObject(stopQmlString, topPanelRoot)
|
||||
|
||||
var cleanupTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; running: true; repeat: false }', topPanelRoot)
|
||||
cleanupTimer.triggered.connect(function() {
|
||||
if (recordingProcess) {
|
||||
recordingProcess.running = false
|
||||
recordingProcess.destroy()
|
||||
recordingProcess = null
|
||||
}
|
||||
|
||||
var forceKillQml = 'import Quickshell.Io; Process { command: ["sh", "-c", "pkill -9 -f \'gpu-screen-recorder.*portal\' 2>/dev/null || true"]; running: true; onExited: function() { destroy() } }'
|
||||
var forceKillProcess = Qt.createQmlObject(forceKillQml, topPanelRoot)
|
||||
|
||||
cleanupTimer.destroy()
|
||||
})
|
||||
}
|
||||
isRecording = false
|
||||
}
|
||||
|
||||
// System action router (lock, reboot, shutdown)
|
||||
function performSystemAction(action) {
|
||||
switch(action) {
|
||||
case "lock":
|
||||
Core.ProcessManager.lock()
|
||||
break
|
||||
case "reboot":
|
||||
Core.ProcessManager.reboot()
|
||||
break
|
||||
case "shutdown":
|
||||
Core.ProcessManager.shutdown()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function performPerformanceAction(action) {
|
||||
// Performance actions handled silently
|
||||
}
|
||||
|
||||
// Clean up processes on destruction
|
||||
Component.onDestruction: {
|
||||
if (recordingProcess) {
|
||||
recordingProcess.running = false
|
||||
recordingProcess.destroy()
|
||||
recordingProcess = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Calendar button for the top panel
|
||||
Rectangle {
|
||||
id: calendarButton
|
||||
width: 40
|
||||
height: 80
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 20
|
||||
|
||||
property bool containsMouse: calendarMouseArea.containsMouse
|
||||
property bool calendarVisible: false
|
||||
property var calendarPopup: null
|
||||
property var shell: null // Shell reference from parent
|
||||
|
||||
signal entered()
|
||||
signal exited()
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: calendarMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
toggleCalendar()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "calendar_month"
|
||||
font.pixelSize: 24
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: calendarButton.containsMouse || calendarButton.calendarVisible ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
}
|
||||
|
||||
function toggleCalendar() {
|
||||
if (!calendarPopup) {
|
||||
var component = Qt.createComponent("root:/Widgets/Calendar/CalendarPopup.qml")
|
||||
if (component.status === Component.Ready) {
|
||||
calendarPopup = component.createObject(calendarButton.parent, {
|
||||
"targetX": calendarButton.x + calendarButton.width + 10,
|
||||
"shell": calendarButton.shell
|
||||
})
|
||||
} else if (component.status === Component.Error) {
|
||||
console.log("Error loading calendar:", component.errorString())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (calendarPopup) {
|
||||
calendarVisible = !calendarVisible
|
||||
calendarPopup.setClickMode(calendarVisible)
|
||||
}
|
||||
}
|
||||
|
||||
function hideCalendar() {
|
||||
if (calendarPopup) {
|
||||
calendarVisible = false
|
||||
calendarPopup.setClickMode(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
Row {
|
||||
id: root
|
||||
spacing: 16
|
||||
visible: true
|
||||
height: 80
|
||||
|
||||
required property bool isRecording
|
||||
required property var shell
|
||||
signal performanceActionRequested(string action)
|
||||
signal systemActionRequested(string action)
|
||||
signal mouseChanged(bool containsMouse)
|
||||
|
||||
// Add hover tracking property
|
||||
property bool containsMouse: performanceSection.containsMouse || systemSection.containsMouse
|
||||
onContainsMouseChanged: mouseChanged(containsMouse)
|
||||
|
||||
Rectangle {
|
||||
id: performanceSection
|
||||
width: (parent.width - parent.spacing) / 2
|
||||
height: parent.height
|
||||
radius: 20
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
visible: true
|
||||
|
||||
// Add hover tracking for performance section
|
||||
property bool containsMouse: performanceMouseArea.containsMouse || performanceControls.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: performanceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
performanceSection.containsMouse = true
|
||||
} else if (!performanceControls.containsMouse) {
|
||||
performanceSection.containsMouse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceControls {
|
||||
id: performanceControls
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
shell: root.shell
|
||||
onPerformanceActionRequested: function(action) { root.performanceActionRequested(action) }
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (containsMouse) {
|
||||
performanceSection.containsMouse = true
|
||||
} else if (!performanceMouseArea.containsMouse) {
|
||||
performanceSection.containsMouse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: systemSection
|
||||
width: (parent.width - parent.spacing) / 2
|
||||
height: parent.height
|
||||
radius: 20
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
visible: true
|
||||
|
||||
// Add hover tracking for system section
|
||||
property bool containsMouse: systemMouseArea.containsMouse || systemControls.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: systemMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
systemSection.containsMouse = true
|
||||
} else if (!systemControls.containsMouse) {
|
||||
systemSection.containsMouse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SystemControls {
|
||||
id: systemControls
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
shell: root.shell
|
||||
onSystemActionRequested: function(action) { root.systemActionRequested(action) }
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (containsMouse) {
|
||||
systemSection.containsMouse = true
|
||||
} else if (!systemMouseArea.containsMouse) {
|
||||
systemSection.containsMouse = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 42
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 12
|
||||
z: 2 // Keep it above notification history
|
||||
|
||||
required property bool notificationHistoryVisible
|
||||
required property bool clipboardHistoryVisible
|
||||
required property var notificationHistory
|
||||
signal notificationToggleRequested()
|
||||
signal clipboardToggleRequested()
|
||||
|
||||
// Add containsMouse property for panel hover tracking
|
||||
property bool containsMouse: notifButtonMouseArea.containsMouse || clipButtonMouseArea.containsMouse
|
||||
|
||||
// Ensure minimum height for buttons even when recording
|
||||
property real buttonHeight: 38
|
||||
height: buttonHeight * 2 + 4 // 4px spacing between buttons
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
|
||||
// Top pill (Notifications)
|
||||
Rectangle {
|
||||
id: notificationPill
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.verticalCenter
|
||||
bottomMargin: 2 // Half of the 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
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom pill (Clipboard)
|
||||
Rectangle {
|
||||
id: clipboardPill
|
||||
anchors {
|
||||
top: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
topMargin: 2 // Half of the 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,704 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import "root:/Data" as Data
|
||||
import "root:/Core" as Core
|
||||
import "root:/Widgets/System" as System
|
||||
import "root:/Widgets/Notifications" as Notifications
|
||||
import "." as Modules
|
||||
|
||||
// Main tabbed panel
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Size calculation
|
||||
width: mainContainer.implicitWidth + 18
|
||||
height: mainContainer.implicitHeight + 18
|
||||
|
||||
required property var shell
|
||||
|
||||
property bool isShown: false
|
||||
property int currentTab: 0 // 0=main, 1=calendar, 2=clipboard, 3=notifications, 4=wallpapers
|
||||
property real bgOpacity: 0.0
|
||||
property bool isRecording: false
|
||||
|
||||
property var tabIcons: ["widgets", "calendar_month", "content_paste", "notifications", "wallpaper"]
|
||||
|
||||
signal recordingRequested()
|
||||
signal stopRecordingRequested()
|
||||
signal systemActionRequested(string action)
|
||||
signal performanceActionRequested(string action)
|
||||
|
||||
// Animation state management
|
||||
visible: opacity > 0
|
||||
opacity: 0
|
||||
x: width
|
||||
|
||||
property var tabNames: ["Main", "Calendar", "Clipboard", "Notifications", "Wallpapers"]
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
// Background with bottom-only rounded corners
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Data.ThemeManager.bgColor
|
||||
topLeftRadius: 0
|
||||
topRightRadius: 0
|
||||
bottomLeftRadius: 20
|
||||
bottomRightRadius: 20
|
||||
}
|
||||
|
||||
// Shadow effect preparation
|
||||
Rectangle {
|
||||
id: shadowSource
|
||||
anchors.fill: mainContainer
|
||||
color: "transparent"
|
||||
visible: false
|
||||
bottomLeftRadius: 20
|
||||
bottomRightRadius: 20
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: shadowSource
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 2
|
||||
radius: 8.0
|
||||
samples: 17
|
||||
color: "#80000000"
|
||||
source: shadowSource
|
||||
z: 1
|
||||
}
|
||||
|
||||
// Main container with tab-based content layout
|
||||
Rectangle {
|
||||
id: mainContainer
|
||||
anchors.fill: parent
|
||||
anchors.margins: 9
|
||||
color: "transparent"
|
||||
radius: 12
|
||||
|
||||
implicitWidth: 600 // Fixed width for consistency
|
||||
implicitHeight: 360
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backgroundMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
// Left sidebar with tab navigation
|
||||
Item {
|
||||
id: tabSidebar
|
||||
width: 40
|
||||
height: parent.height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 9
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 54
|
||||
|
||||
property bool containsMouse: sidebarMouseArea.containsMouse || tabColumn.containsMouse
|
||||
|
||||
// Tab button background
|
||||
Rectangle {
|
||||
width: 36
|
||||
height: tabColumn.height + 8
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.05)
|
||||
radius: 18
|
||||
border.color: Qt.darker(Data.ThemeManager.bgColor, 1.2)
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sidebarMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onEntered: hideTimer.stop()
|
||||
onExited: {
|
||||
if (!root.isHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab icon buttons
|
||||
Column {
|
||||
id: tabColumn
|
||||
spacing: 4
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
property bool containsMouse: {
|
||||
for (let i = 0; i < tabRepeater.count; i++) {
|
||||
let tab = tabRepeater.itemAt(i)
|
||||
if (tab && tab.children[0] && tab.children[0].containsMouse) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: tabRepeater
|
||||
model: 5
|
||||
delegate: Rectangle {
|
||||
width: 32
|
||||
height: 32
|
||||
radius: 16
|
||||
color: currentTab === index ? Data.ThemeManager.accentColor : Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
|
||||
property bool isHovered: tabMouseArea.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: tabMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.currentTab = index
|
||||
onEntered: hideTimer.stop()
|
||||
onExited: {
|
||||
if (!root.isHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.tabIcons[index]
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: currentTab === index ? Data.ThemeManager.bgColor :
|
||||
parent.isHovered ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area (positioned right of tab sidebar)
|
||||
Column {
|
||||
id: mainColumn
|
||||
width: parent.width - tabSidebar.width - 45
|
||||
anchors.left: tabSidebar.right
|
||||
anchors.leftMargin: 9
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 18
|
||||
spacing: 28
|
||||
clip: true
|
||||
|
||||
// Tab 0: Main dashboard content
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 28
|
||||
visible: root.currentTab === 0
|
||||
|
||||
// User profile row with theme toggle and weather
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 18
|
||||
|
||||
UserProfile {
|
||||
id: userProfile
|
||||
width: parent.width - themeToggle.width - weatherDisplay.width - (parent.spacing * 2)
|
||||
height: 80
|
||||
shell: root.shell
|
||||
}
|
||||
|
||||
ThemeToggle {
|
||||
id: themeToggle
|
||||
width: 40
|
||||
height: userProfile.height
|
||||
}
|
||||
|
||||
WeatherDisplay {
|
||||
id: weatherDisplay
|
||||
width: parent.width * 0.18
|
||||
height: userProfile.height
|
||||
shell: root.shell
|
||||
onEntered: hideTimer.stop()
|
||||
onExited: hideTimer.restart()
|
||||
visible: root.visible
|
||||
enabled: visible
|
||||
}
|
||||
}
|
||||
|
||||
// Controls section
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 18
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 28
|
||||
|
||||
RecordingButton {
|
||||
id: recordingButton
|
||||
width: parent.width
|
||||
height: 48
|
||||
shell: root.shell
|
||||
isRecording: root.isRecording
|
||||
|
||||
onRecordingRequested: root.recordingRequested()
|
||||
onStopRecordingRequested: root.stopRecordingRequested()
|
||||
}
|
||||
|
||||
Controls {
|
||||
id: controls
|
||||
width: parent.width
|
||||
isRecording: root.isRecording
|
||||
shell: root.shell
|
||||
onPerformanceActionRequested: function(action) { root.performanceActionRequested(action) }
|
||||
onSystemActionRequested: function(action) { root.systemActionRequested(action) }
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (containsMouse) {
|
||||
hideTimer.stop()
|
||||
} else if (!root.isHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System tray section with inline menu
|
||||
Column {
|
||||
id: systemTraySection
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
property bool containsMouse: trayMouseArea.containsMouse || systemTrayModule.containsMouse
|
||||
|
||||
Rectangle {
|
||||
id: trayBackground
|
||||
width: parent.width
|
||||
height: 40
|
||||
radius: 20
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
|
||||
property bool isActive: false
|
||||
|
||||
MouseArea {
|
||||
id: trayMouseArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: -10
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
preventStealing: false
|
||||
onEntered: trayBackground.isActive = true
|
||||
onExited: {
|
||||
if (!inlineTrayMenu.visible) {
|
||||
Qt.callLater(function() {
|
||||
if (!systemTrayModule.containsMouse) {
|
||||
trayBackground.isActive = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.SystemTray {
|
||||
id: systemTrayModule
|
||||
anchors.centerIn: parent
|
||||
shell: root.shell
|
||||
bar: parent
|
||||
trayMenu: inlineTrayMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrayMenu {
|
||||
id: inlineTrayMenu
|
||||
parent: mainContainer
|
||||
width: parent.width
|
||||
menu: null
|
||||
systemTrayY: systemTraySection.y
|
||||
systemTrayHeight: systemTraySection.height
|
||||
onHideRequested: trayBackground.isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 1: Calendar content with lazy loading
|
||||
Column {
|
||||
width: parent.width
|
||||
height: 310
|
||||
visible: root.currentTab === 1
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Calendar"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
font.family: "FiraCode Nerd Font"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - parent.children[0].height - parent.spacing
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.2)
|
||||
radius: 20
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
active: visible && root.currentTab === 1
|
||||
source: active ? "root:/Widgets/Calendar/Calendar.qml" : ""
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.shell = root.shell
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 2: Clipboard history with clear button
|
||||
Column {
|
||||
width: parent.width
|
||||
height: 310
|
||||
visible: root.currentTab === 2
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Clipboard History"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
font.family: "Roboto"
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Rectangle {
|
||||
width: clearClipText.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 12
|
||||
color: clearClipMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : "transparent"
|
||||
border.color: Data.ThemeManager.accentColor
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: clearClipText
|
||||
anchors.centerIn: parent
|
||||
text: "Clear All"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearClipMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
// Navigate to clipboard component and call clear
|
||||
let clipLoader = parent.parent.parent.children[1].children[0]
|
||||
if (clipLoader && clipLoader.item && clipLoader.item.children[0]) {
|
||||
let clipComponent = clipLoader.item.children[0]
|
||||
if (clipComponent.clearClipboardHistory) {
|
||||
clipComponent.clearClipboardHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - parent.children[0].height - parent.spacing
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.2)
|
||||
radius: 20
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
active: visible && root.currentTab === 2
|
||||
sourceComponent: active ? clipboardHistoryComponent : null
|
||||
onLoaded: {
|
||||
if (item && item.children[0]) {
|
||||
item.children[0].refreshClipboardHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 3: Notification history with clear button
|
||||
Column {
|
||||
width: parent.width
|
||||
height: 310
|
||||
visible: root.currentTab === 3
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Notification History"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
font.family: "Roboto"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "(" + (root.shell.notificationHistory ? root.shell.notificationHistory.count : 0) + ")"
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 12
|
||||
opacity: 0.7
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Rectangle {
|
||||
width: clearNotifText.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 12
|
||||
color: clearNotifMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : "transparent"
|
||||
border.color: Data.ThemeManager.accentColor
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: clearNotifText
|
||||
anchors.centerIn: parent
|
||||
text: "Clear All"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearNotifMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.shell.notificationHistory.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - parent.children[0].height - parent.spacing
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.2)
|
||||
radius: 20
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
active: visible && root.currentTab === 3
|
||||
sourceComponent: active ? notificationHistoryComponent : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 4: Wallpaper selector
|
||||
Column {
|
||||
width: parent.width
|
||||
height: 310
|
||||
visible: root.currentTab === 4
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Wallpapers"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
font.family: "Roboto"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - parent.children[0].height - parent.spacing
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.2)
|
||||
radius: 20
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 20
|
||||
active: visible && root.currentTab === 4
|
||||
sourceComponent: active ? wallpaperSelectorComponent : null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy-loaded components for tab content
|
||||
Component {
|
||||
id: clipboardHistoryComponent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
System.Cliphist {
|
||||
id: cliphistComponent
|
||||
anchors.fill: parent
|
||||
shell: root.shell
|
||||
|
||||
// Hide built-in header (we provide our own)
|
||||
Component.onCompleted: {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i]
|
||||
if (child.objectName === "contentColumn" || child.toString().includes("ColumnLayout")) {
|
||||
if (child.children && child.children.length > 0) {
|
||||
child.children[0].visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notificationHistoryComponent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Notifications.NotificationHistory {
|
||||
anchors.fill: parent
|
||||
shell: root.shell
|
||||
clip: true
|
||||
|
||||
// Hide built-in header (we provide our own)
|
||||
Component.onCompleted: {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i]
|
||||
if (child.objectName === "contentColumn" || child.toString().includes("ColumnLayout")) {
|
||||
if (child.children && child.children.length > 0) {
|
||||
child.children[0].visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: wallpaperSelectorComponent
|
||||
Modules.WallpaperSelector {
|
||||
isVisible: parent && parent.parent && parent.parent.visible
|
||||
}
|
||||
}
|
||||
|
||||
// Complex hover state calculation for auto-hide behavior
|
||||
property bool isHovered: {
|
||||
const menuStates = {
|
||||
inlineMenuActive: inlineTrayMenu.menuJustOpened || inlineTrayMenu.visible,
|
||||
trayActive: trayBackground.isActive,
|
||||
tabContentActive: currentTab !== 0
|
||||
}
|
||||
|
||||
if (menuStates.inlineMenuActive || menuStates.trayActive || menuStates.tabContentActive) return true
|
||||
|
||||
const mouseStates = {
|
||||
backgroundHovered: backgroundMouseArea.containsMouse,
|
||||
recordingHovered: recordingButton.containsMouse,
|
||||
controlsHovered: controls.containsMouse,
|
||||
profileHovered: userProfile.isHovered,
|
||||
themeToggleHovered: themeToggle.containsMouse,
|
||||
systemTrayHovered: systemTraySection.containsMouse ||
|
||||
trayMouseArea.containsMouse ||
|
||||
systemTrayModule.containsMouse,
|
||||
menuHovered: inlineTrayMenu.containsMouse,
|
||||
weatherHovered: weatherDisplay.containsMouse,
|
||||
tabSidebarHovered: tabSidebar.containsMouse,
|
||||
mainContentHovered: mainColumn.children[0].visible && backgroundMouseArea.containsMouse
|
||||
}
|
||||
|
||||
return Object.values(mouseStates).some(state => state)
|
||||
}
|
||||
|
||||
// Auto-hide timer
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: hide()
|
||||
}
|
||||
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
hideTimer.stop()
|
||||
} else if (!inlineTrayMenu.visible && !trayBackground.isActive && !tabSidebar.containsMouse && !tabColumn.containsMouse) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (isShown) return
|
||||
isShown = true
|
||||
hideTimer.stop()
|
||||
opacity = 1
|
||||
x = 0
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (!isShown || inlineTrayMenu.menuJustOpened || inlineTrayMenu.visible) return
|
||||
// Only hide on main tab when nothing is hovered
|
||||
if (currentTab === 0 && !isHovered) {
|
||||
isShown = false
|
||||
x = width
|
||||
opacity = 0
|
||||
|
||||
// Hide parent TopPanel as well
|
||||
if (parent && parent.parent && parent.parent.hide) {
|
||||
parent.parent.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(function() {
|
||||
mainColumn.visible = true
|
||||
})
|
||||
}
|
||||
|
||||
// Border integration corners
|
||||
Core.Corners {
|
||||
id: topLeftCorner
|
||||
position: "bottomright"
|
||||
size: 1.3
|
||||
fillColor: Data.ThemeManager.bgColor
|
||||
offsetX: 0
|
||||
offsetY: 0
|
||||
}
|
||||
|
||||
Core.Corners {
|
||||
id: topRightCorner
|
||||
position: "bottomleft"
|
||||
size: 1.3
|
||||
fillColor: Data.ThemeManager.bgColor
|
||||
offsetX: root.width
|
||||
offsetY: 0
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell.Services.UPower
|
||||
|
||||
Column {
|
||||
id: root
|
||||
required property var shell
|
||||
|
||||
spacing: 8
|
||||
signal performanceActionRequested(string action)
|
||||
signal mouseChanged(bool containsMouse)
|
||||
|
||||
readonly property bool containsMouse: performanceButton.containsMouse ||
|
||||
balancedButton.containsMouse ||
|
||||
powerSaverButton.containsMouse
|
||||
|
||||
// Safe property access with fallbacks
|
||||
readonly property bool upowerReady: typeof PowerProfiles !== 'undefined' && PowerProfiles
|
||||
readonly property int currentProfile: upowerReady ? PowerProfiles.profile : 0
|
||||
|
||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
||||
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 8
|
||||
width: parent.width
|
||||
|
||||
// Performance Profile Button
|
||||
SystemButton {
|
||||
id: performanceButton
|
||||
width: (parent.width - parent.spacing * 2) / 3
|
||||
height: 52
|
||||
|
||||
shell: root.shell
|
||||
iconText: "speed"
|
||||
|
||||
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ?
|
||||
root.currentProfile === PowerProfile.Performance : false
|
||||
|
||||
onClicked: {
|
||||
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
|
||||
PowerProfiles.profile = PowerProfile.Performance
|
||||
root.performanceActionRequested("performance")
|
||||
} else {
|
||||
console.warn("PowerProfiles not available")
|
||||
}
|
||||
}
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balanced Profile Button
|
||||
SystemButton {
|
||||
id: balancedButton
|
||||
width: (parent.width - parent.spacing * 2) / 3
|
||||
height: 52
|
||||
|
||||
shell: root.shell
|
||||
iconText: "balance"
|
||||
|
||||
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ?
|
||||
root.currentProfile === PowerProfile.Balanced : false
|
||||
|
||||
onClicked: {
|
||||
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
|
||||
PowerProfiles.profile = PowerProfile.Balanced
|
||||
root.performanceActionRequested("balanced")
|
||||
} else {
|
||||
console.warn("PowerProfiles not available")
|
||||
}
|
||||
}
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power Saver Profile Button
|
||||
SystemButton {
|
||||
id: powerSaverButton
|
||||
width: (parent.width - parent.spacing * 2) / 3
|
||||
height: 52
|
||||
|
||||
shell: root.shell
|
||||
iconText: "battery_saver"
|
||||
|
||||
isActive: root.upowerReady && (typeof PowerProfile !== 'undefined') ?
|
||||
root.currentProfile === PowerProfile.PowerSaver : false
|
||||
|
||||
onClicked: {
|
||||
if (root.upowerReady && typeof PowerProfile !== 'undefined') {
|
||||
PowerProfiles.profile = PowerProfile.PowerSaver
|
||||
root.performanceActionRequested("powersaver")
|
||||
} else {
|
||||
console.warn("PowerProfiles not available")
|
||||
}
|
||||
}
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Add a small delay to ensure services are ready
|
||||
Component.onCompleted: {
|
||||
// Small delay to ensure UPower service is fully initialized
|
||||
Qt.callLater(function() {
|
||||
if (!root.upowerReady) {
|
||||
console.warn("UPower service not ready - performance controls may not work correctly")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var shell
|
||||
required property bool isRecording
|
||||
radius: 20
|
||||
|
||||
signal recordingRequested()
|
||||
signal stopRecordingRequested()
|
||||
signal mouseChanged(bool containsMouse)
|
||||
|
||||
// Gray by default, accent color on hover or when recording
|
||||
color: isRecording ? Data.ThemeManager.accentColor :
|
||||
(mouseArea.containsMouse ? Data.ThemeManager.accentColor : Qt.darker(Data.ThemeManager.bgColor, 1.15))
|
||||
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
readonly property alias containsMouse: mouseArea.containsMouse
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: isRecording ? "stop_circle" : "radio_button_unchecked"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
color: isRecording || mouseArea.containsMouse ? "#ffffff" : Data.ThemeManager.fgColor
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: isRecording ? "Stop Recording" : "Start Recording"
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
color: isRecording || mouseArea.containsMouse ? "#ffffff" : Data.ThemeManager.fgColor
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
||||
|
||||
onClicked: {
|
||||
if (isRecording) {
|
||||
root.stopRecordingRequested()
|
||||
} else {
|
||||
root.recordingRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var shell
|
||||
required property string iconText
|
||||
property string labelText: ""
|
||||
|
||||
// Add active state property
|
||||
property bool isActive: false
|
||||
|
||||
radius: 20
|
||||
|
||||
// Modified color logic to handle active state
|
||||
color: {
|
||||
if (isActive) {
|
||||
return mouseArea.containsMouse ?
|
||||
Qt.lighter(Data.ThemeManager.accentColor, 1.1) :
|
||||
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
|
||||
} else {
|
||||
return mouseArea.containsMouse ?
|
||||
Qt.lighter(Data.ThemeManager.accentColor, 1.2) :
|
||||
Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
||||
}
|
||||
}
|
||||
|
||||
border.width: isActive ? 2 : 1
|
||||
border.color: isActive ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
|
||||
signal clicked()
|
||||
signal mouseChanged(bool containsMouse)
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
readonly property alias containsMouse: mouseArea.containsMouse
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
scale: isHovered ? 1.05 : 1.0
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: root.iconText
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 16
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: {
|
||||
if (root.isActive) {
|
||||
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor
|
||||
} else {
|
||||
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: root.labelText
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 8
|
||||
color: {
|
||||
if (root.isActive) {
|
||||
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor
|
||||
} else {
|
||||
return root.isHovered ? "#ffffff" : Data.ThemeManager.accentColor
|
||||
}
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
font.weight: root.isActive ? Font.Bold : Font.Medium
|
||||
visible: root.labelText !== ""
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
required property var shell
|
||||
|
||||
spacing: 8
|
||||
signal systemActionRequested(string action)
|
||||
signal mouseChanged(bool containsMouse)
|
||||
|
||||
readonly property bool containsMouse: lockButton.containsMouse ||
|
||||
rebootButton.containsMouse ||
|
||||
shutdownButton.containsMouse
|
||||
|
||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
||||
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Lock Button
|
||||
SystemButton {
|
||||
id: lockButton
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
shell: root.shell
|
||||
iconText: "lock"
|
||||
|
||||
onClicked: {
|
||||
console.log("Lock button clicked")
|
||||
console.log("root.shell:", root.shell)
|
||||
console.log("root.shell.lockscreen:", root.shell ? root.shell.lockscreen : "shell is null")
|
||||
|
||||
// Directly trigger custom lockscreen
|
||||
if (root.shell && root.shell.lockscreen) {
|
||||
console.log("Calling root.shell.lockscreen.lock()")
|
||||
root.shell.lockscreen.lock()
|
||||
} else {
|
||||
console.log("Fallback to systemActionRequested")
|
||||
// Fallback to system action for compatibility
|
||||
root.systemActionRequested("lock")
|
||||
}
|
||||
}
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reboot Button
|
||||
SystemButton {
|
||||
id: rebootButton
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
shell: root.shell
|
||||
iconText: "restart_alt"
|
||||
|
||||
onClicked: root.systemActionRequested("reboot")
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown Button
|
||||
SystemButton {
|
||||
id: shutdownButton
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
shell: root.shell
|
||||
iconText: "power_settings_new"
|
||||
|
||||
onClicked: root.systemActionRequested("shutdown")
|
||||
onMouseChanged: function(containsMouse) {
|
||||
if (!containsMouse && !root.containsMouse) {
|
||||
root.mouseChanged(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property var shell: null
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 20
|
||||
|
||||
property bool containsMouse: themeMouseArea.containsMouse
|
||||
property bool menuJustOpened: false
|
||||
|
||||
signal entered()
|
||||
signal exited()
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else if (!menuJustOpened) {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: themeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
Data.ThemeManager.toggleTheme()
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "contrast"
|
||||
font.pixelSize: 24
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 360
|
||||
height: 1
|
||||
color: "red"
|
||||
anchors.top: parent.top
|
||||
|
||||
signal triggered()
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
property bool isHovered: containsMouse
|
||||
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
showTimer.start()
|
||||
hideTimer.stop()
|
||||
} else {
|
||||
hideTimer.start()
|
||||
showTimer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: hideTimer.stop()
|
||||
}
|
||||
|
||||
// Smooth show/hide timers
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: 200
|
||||
onTriggered: root.triggered()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 500
|
||||
}
|
||||
|
||||
// Exposed properties and functions
|
||||
readonly property alias containsMouse: mouseArea.containsMouse
|
||||
function stopHideTimer() { hideTimer.stop() }
|
||||
function startHideTimer() { hideTimer.start() }
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: parent.width
|
||||
height: visible ? calculatedHeight : 0
|
||||
visible: false
|
||||
enabled: visible
|
||||
clip: true
|
||||
color: Data.ThemeManager.bgColor
|
||||
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
|
||||
hideRequested()
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
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++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var separatorHeight = separatorCount * 12
|
||||
var regularItemRows = Math.ceil(regularItemCount / 2)
|
||||
var regularItemHeight = regularItemRows * 32
|
||||
return Math.max(80, 35 + separatorHeight + regularItemHeight + 40)
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: opener
|
||||
menu: root.menu
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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: "FiraCode Nerd Font"
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: "FiraCode Nerd Font"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import "root:/Data/" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var shell
|
||||
property url avatarSource: Data.Settings.avatarSource
|
||||
property string userName: "" // will be set by process output
|
||||
property string userInfo: "" // will hold uptime string
|
||||
|
||||
property bool isActive: false
|
||||
property bool isHovered: false // track hover state
|
||||
|
||||
radius: 20
|
||||
width: 220
|
||||
height: 80
|
||||
|
||||
color: {
|
||||
if (isActive) {
|
||||
return isHovered ?
|
||||
Qt.lighter(Data.ThemeManager.accentColor, 1.1) :
|
||||
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
|
||||
} else {
|
||||
return isHovered ?
|
||||
Qt.lighter(Data.ThemeManager.accentColor, 1.2) :
|
||||
Qt.lighter(Data.ThemeManager.bgColor, 1.15)
|
||||
}
|
||||
}
|
||||
|
||||
border.width: isActive ? 2 : 1
|
||||
border.color: isActive ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: avatarCircle
|
||||
width: 52
|
||||
height: 52
|
||||
radius: 20
|
||||
clip: true
|
||||
border.color: Data.ThemeManager.accentColor
|
||||
border.width: 3
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
id: avatarImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
source: Data.Settings.avatarSource
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
cache: false
|
||||
visible: false // Hide the original image
|
||||
asynchronous: true
|
||||
sourceSize.width: 48 // Limit image resolution to save memory
|
||||
sourceSize.height: 48
|
||||
}
|
||||
|
||||
OpacityMask {
|
||||
anchors.fill: avatarImage
|
||||
source: avatarImage
|
||||
maskSource: Rectangle {
|
||||
width: avatarImage.width
|
||||
height: avatarImage.height
|
||||
radius: 18 // Proportionally smaller than parent (48/52 * 20 ≈ 18)
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
spacing: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - avatarCircle.width - gifContainer.width - parent.spacing * 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.userName === "" ? "Loading..." : root.userName
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
color: isHovered || root.isActive ? Data.ThemeManager.bgColor : Data.ThemeManager.accentColor
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.userInfo === "" ? "Loading uptime..." : root.userInfo
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
color: isHovered || root.isActive ? "#cccccc" : Qt.lighter(Data.ThemeManager.accentColor, 1.6)
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: gifContainer
|
||||
width: 80
|
||||
height: 80
|
||||
radius: 12
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
AnimatedImage {
|
||||
id: animatedImage
|
||||
source: "root:/Assets/UserProfile.gif"
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
playing: true
|
||||
cache: false
|
||||
speed: 1.0
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
// Always enable layer effects for rounded corners
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: gifContainer.width
|
||||
height: gifContainer.height
|
||||
radius: gifContainer.radius
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: root.isHovered = true
|
||||
onExited: root.isHovered = false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: usernameProcess
|
||||
running: true // Always run to get username
|
||||
command: ["sh", "-c", "whoami"]
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
const line = data.trim();
|
||||
if (line.length > 0) {
|
||||
root.userName = line.charAt(0).toUpperCase() + line.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: uptimeProcess
|
||||
running: false
|
||||
command: ["sh", "-c", "uptime"] // Use basic uptime command
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
const line = data.trim();
|
||||
if (line.length > 0) {
|
||||
// Parse traditional uptime output: " 10:30:00 up 1:23, 2 users, load average: 0.08, 0.02, 0.01"
|
||||
const match = line.match(/up\s+(.+?),\s+\d+\s+user/);
|
||||
if (match && match[1]) {
|
||||
root.userInfo = "Up: " + match[1].trim();
|
||||
} else {
|
||||
// Fallback parsing
|
||||
const upIndex = line.indexOf("up ");
|
||||
if (upIndex !== -1) {
|
||||
const afterUp = line.substring(upIndex + 3);
|
||||
const commaIndex = afterUp.indexOf(",");
|
||||
if (commaIndex !== -1) {
|
||||
root.userInfo = "Up: " + afterUp.substring(0, commaIndex).trim();
|
||||
} else {
|
||||
root.userInfo = "Up: " + afterUp.trim();
|
||||
}
|
||||
} else {
|
||||
root.userInfo = "Uptime unknown";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
root.userInfo = "Uptime unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
console.log("Uptime error:", data);
|
||||
root.userInfo = "Uptime error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: uptimeTimer
|
||||
interval: 300000 // Update every 5 minutes
|
||||
running: true // Always run the uptime timer
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
uptimeProcess.running = false
|
||||
uptimeProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
uptimeProcess.running = true // Start uptime process on component load
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (usernameProcess.running) {
|
||||
usernameProcess.running = false
|
||||
}
|
||||
if (uptimeProcess.running) {
|
||||
uptimeProcess.running = false
|
||||
}
|
||||
if (uptimeTimer.running) {
|
||||
uptimeTimer.running = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import "root:/Data" as Data
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool isVisible: false
|
||||
signal visibilityChanged(bool visible)
|
||||
|
||||
// Use all space provided by parent
|
||||
anchors.fill: parent
|
||||
visible: isVisible
|
||||
enabled: visible
|
||||
clip: true
|
||||
|
||||
property bool containsMouse: wallpaperSelectorMouseArea.containsMouse || scrollView.containsMouse
|
||||
property bool menuJustOpened: false
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
hideTimer.stop()
|
||||
} else if (!menuJustOpened && !isVisible) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
menuJustOpened = true
|
||||
hideTimer.stop()
|
||||
Qt.callLater(function() {
|
||||
menuJustOpened = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: wallpaperSelectorMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: false
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
// Wallpaper grid - use all available space
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
property bool containsMouse: gridMouseArea.containsMouse
|
||||
|
||||
MouseArea {
|
||||
id: gridMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: wallpaperGrid
|
||||
anchors.fill: parent
|
||||
cellWidth: parent.width / 2 - 8 // 2 columns with spacing for bigger previews
|
||||
cellHeight: cellWidth * 0.6 // Aspect ratio for wallpapers
|
||||
model: Data.WallpaperManager.wallpaperList
|
||||
cacheBuffer: 0 // Disable cache buffer to save massive memory
|
||||
leftMargin: 4
|
||||
rightMargin: 4
|
||||
topMargin: 4
|
||||
bottomMargin: 4
|
||||
|
||||
delegate: Item {
|
||||
width: wallpaperGrid.cellWidth - 8
|
||||
height: wallpaperGrid.cellHeight - 8
|
||||
|
||||
Rectangle {
|
||||
id: wallpaperItem
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.2)
|
||||
radius: 20
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: wallpaperImage
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
source: modelData
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: false // Disable caching to save massive memory
|
||||
sourceSize.width: Math.min(width, 150) // Further reduced from 200 to 150
|
||||
sourceSize.height: Math.min(height, 90) // Further reduced from 120 to 90
|
||||
|
||||
// Only load when item is visible in viewport
|
||||
visible: parent.parent.y >= wallpaperGrid.contentY - parent.parent.height &&
|
||||
parent.parent.y <= wallpaperGrid.contentY + wallpaperGrid.height
|
||||
|
||||
// Disable layer effects to save memory
|
||||
// layer.enabled: true
|
||||
// layer.effect: OpacityMask {
|
||||
// maskSource: Rectangle {
|
||||
// width: wallpaperImage.width
|
||||
// height: wallpaperImage.height
|
||||
// radius: 18 // Slightly smaller than parent to account for margins
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// Selected indicator
|
||||
Rectangle {
|
||||
visible: modelData === Data.WallpaperManager.currentWallpaper
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
border.color: Data.ThemeManager.accentColor
|
||||
border.width: 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: wallpaperItem.scale = 1.05
|
||||
onExited: wallpaperItem.scale = 1.0
|
||||
onClicked: {
|
||||
Data.WallpaperManager.setWallpaper(modelData)
|
||||
// Removed the close behavior - stays in wallpaper tab
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Use lazy loading to only load wallpapers when this component is actually used
|
||||
Data.WallpaperManager.ensureWallpapersLoaded()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,323 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var shell
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 20
|
||||
|
||||
property bool containsMouse: weatherMouseArea.containsMouse || (forecastPopup.visible && forecastPopup.containsMouse)
|
||||
property bool menuJustOpened: false
|
||||
|
||||
signal entered()
|
||||
signal exited()
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else if (!menuJustOpened && !forecastPopup.visible) {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
function getWeatherIcon(condition) {
|
||||
if (!condition) return "light_mode"
|
||||
|
||||
const c = condition.toString()
|
||||
|
||||
const iconMap = {
|
||||
"0": "light_mode",
|
||||
"1": "light_mode",
|
||||
"2": "cloud",
|
||||
"3": "cloud",
|
||||
"45": "foggy",
|
||||
"48": "foggy",
|
||||
"51": "water_drop",
|
||||
"53": "water_drop",
|
||||
"55": "water_drop",
|
||||
"61": "water_drop",
|
||||
"63": "water_drop",
|
||||
"65": "water_drop",
|
||||
"71": "ac_unit",
|
||||
"73": "ac_unit",
|
||||
"75": "ac_unit",
|
||||
"80": "water_drop",
|
||||
"81": "water_drop",
|
||||
"82": "water_drop",
|
||||
"95": "thunderstorm",
|
||||
"96": "thunderstorm",
|
||||
"99": "thunderstorm"
|
||||
}
|
||||
|
||||
if (iconMap[c]) return iconMap[c]
|
||||
|
||||
const textMap = {
|
||||
"clear sky": "light_mode",
|
||||
"mainly clear": "light_mode",
|
||||
"partly cloudy": "cloud",
|
||||
"overcast": "cloud",
|
||||
"fog": "foggy",
|
||||
"drizzle": "water_drop",
|
||||
"rain": "water_drop",
|
||||
"snow": "ac_unit",
|
||||
"thunderstorm": "thunderstorm"
|
||||
}
|
||||
|
||||
const lower = condition.toLowerCase()
|
||||
for (let key in textMap) {
|
||||
if (lower.includes(key)) return textMap[key]
|
||||
}
|
||||
|
||||
return "help"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: weatherMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
menuJustOpened = true
|
||||
forecastPopup.open()
|
||||
Qt.callLater(() => menuJustOpened = false)
|
||||
}
|
||||
onExited: {
|
||||
if (!forecastPopup.containsMouse && !menuJustOpened) {
|
||||
forecastPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: weatherLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Label {
|
||||
text: {
|
||||
if (shell.weatherLoading) return "refresh"
|
||||
if (!shell.weatherData) return "help"
|
||||
return root.getWeatherIcon(shell.weatherData.currentCondition)
|
||||
}
|
||||
font.pixelSize: 28
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: Data.ThemeManager.accentColor
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: {
|
||||
if (shell.weatherLoading) return "Loading..."
|
||||
if (!shell.weatherData) return "No weather data"
|
||||
return shell.weatherData.currentTemp
|
||||
}
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: forecastPopup
|
||||
y: parent.height + 28
|
||||
x: Math.min(0, parent.width - width)
|
||||
width: 300
|
||||
height: 226
|
||||
padding: 12
|
||||
background: Rectangle {
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 20
|
||||
border.width: 1
|
||||
border.color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
property bool containsMouse: forecastMouseArea.containsMouse
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
entered()
|
||||
} else if (!weatherMouseArea.containsMouse && !menuJustOpened) {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: forecastMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onExited: {
|
||||
if (!weatherMouseArea.containsMouse && !menuJustOpened) {
|
||||
forecastPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: forecastColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
text: shell.weatherData ? root.getWeatherIcon(shell.weatherData.currentCondition) : ""
|
||||
font.pixelSize: 48
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: Data.ThemeManager.accentColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
text: shell.weatherData ? shell.weatherData.currentCondition : ""
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Label {
|
||||
text: "thermostat"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 12
|
||||
color: Data.ThemeManager.accentColor
|
||||
}
|
||||
Label {
|
||||
text: shell.weatherData ? shell.weatherData.currentTemp : ""
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 12
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Label {
|
||||
text: "air"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 12
|
||||
color: Data.ThemeManager.accentColor
|
||||
}
|
||||
Label {
|
||||
text: {
|
||||
if (!shell.weatherData || !shell.weatherData.details) return ""
|
||||
const windInfo = shell.weatherData.details.find(d => d.startsWith("Wind:"))
|
||||
return windInfo ? windInfo.split(": ")[1] : ""
|
||||
}
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 12
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Label {
|
||||
text: "explore"
|
||||
font.family: "Material Symbols Outlined"
|
||||
font.pixelSize: 12
|
||||
color: Data.ThemeManager.accentColor
|
||||
}
|
||||
Label {
|
||||
text: {
|
||||
if (!shell.weatherData || !shell.weatherData.details) return ""
|
||||
const dirInfo = shell.weatherData.details.find(d => d.startsWith("Direction:"))
|
||||
return dirInfo ? dirInfo.split(": ")[1] : ""
|
||||
}
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "3-Day Forecast"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: shell.weatherData ? shell.weatherData.forecast : []
|
||||
delegate: Column {
|
||||
width: (parent.width - 16) / 3
|
||||
spacing: 2
|
||||
|
||||
Label {
|
||||
text: modelData.dayName
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: root.getWeatherIcon(modelData.condition)
|
||||
font.pixelSize: 16
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: Data.ThemeManager.accentColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
text: modelData.minTemp + "° - " + modelData.maxTemp + "°"
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.pixelSize: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue