add crypto
This commit is contained in:
parent
90cbe489f6
commit
af6a3bce3e
120 changed files with 24616 additions and 462 deletions
|
|
@ -0,0 +1,75 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Calendar button
|
||||
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()
|
||||
|
||||
// Hover state management
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: calendarMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
toggleCalendar()
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar icon
|
||||
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
|
||||
}
|
||||
|
||||
// Toggle calendar popup
|
||||
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,297 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Night light widget with pure Qt overlay (no external dependencies)
|
||||
Rectangle {
|
||||
id: root
|
||||
property var shell: null
|
||||
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
|
||||
radius: 20
|
||||
|
||||
property bool containsMouse: nightLightMouseArea.containsMouse
|
||||
property bool isActive: Data.Settings.nightLightEnabled
|
||||
property real warmth: Data.Settings.nightLightWarmth // 0=no filter, 1=very warm (0-1 scale)
|
||||
property real strength: isActive ? warmth : 0
|
||||
property bool autoSchedulerActive: false // Flag to prevent manual override during auto changes
|
||||
|
||||
signal entered()
|
||||
signal exited()
|
||||
|
||||
// Night light overlay window
|
||||
property var overlayWindow: null
|
||||
|
||||
// Hover state management for parent components
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
// Background with warm tint when active
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: isActive ? Qt.rgba(1.0, 0.6, 0.2, 0.15) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 300 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nightLightMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
// Right-click to cycle through warmth levels
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: function(mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
cycleWarmth()
|
||||
} else {
|
||||
toggleNightLight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Night light icon with dynamic color
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: isActive ? "light_mode" : "dark_mode"
|
||||
font.pixelSize: 24
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: isActive ?
|
||||
Qt.rgba(1.0, 0.8 - strength * 0.3, 0.4 - strength * 0.2, 1.0) : // Warm orange when active
|
||||
(containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 200 }
|
||||
}
|
||||
}
|
||||
|
||||
// Warmth indicator dots
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 3
|
||||
visible: isActive && containsMouse
|
||||
|
||||
Repeater {
|
||||
model: 3
|
||||
delegate: Rectangle {
|
||||
width: 4
|
||||
height: 4
|
||||
radius: 2
|
||||
color: index < Math.ceil(warmth * 3) ?
|
||||
Qt.rgba(1.0, 0.7 - index * 0.2, 0.3, 0.8) :
|
||||
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for settings changes
|
||||
Connections {
|
||||
target: Data.Settings
|
||||
function onNightLightEnabledChanged() {
|
||||
if (Data.Settings.nightLightEnabled) {
|
||||
createOverlay()
|
||||
} else {
|
||||
removeOverlay()
|
||||
}
|
||||
|
||||
// Set manual override flag if this wasn't an automatic change
|
||||
if (!autoSchedulerActive) {
|
||||
Data.Settings.nightLightManualOverride = true
|
||||
Data.Settings.nightLightManuallyEnabled = Data.Settings.nightLightEnabled
|
||||
console.log("Manual night light change detected - override enabled, manually set to:", Data.Settings.nightLightEnabled)
|
||||
}
|
||||
}
|
||||
function onNightLightWarmthChanged() {
|
||||
updateOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
// Functions to control night light
|
||||
function toggleNightLight() {
|
||||
Data.Settings.nightLightEnabled = !Data.Settings.nightLightEnabled
|
||||
}
|
||||
|
||||
function cycleWarmth() {
|
||||
// Cycle through warmth levels: 0.2 -> 0.4 -> 0.6 -> 1.0 -> 0.2
|
||||
var newWarmth = warmth >= 1.0 ? 0.2 : (warmth >= 0.6 ? 1.0 : warmth + 0.2)
|
||||
Data.Settings.nightLightWarmth = newWarmth
|
||||
}
|
||||
|
||||
function createOverlay() {
|
||||
if (overlayWindow) return
|
||||
|
||||
var qmlString = `
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
PanelWindow {
|
||||
id: nightLightOverlay
|
||||
screen: Quickshell.primaryScreen || Quickshell.screens[0]
|
||||
|
||||
anchors.top: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
anchors.bottom: true
|
||||
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-nightlight"
|
||||
exclusiveZone: 0
|
||||
|
||||
// Click-through overlay
|
||||
mask: Region {}
|
||||
|
||||
Rectangle {
|
||||
id: overlayRect
|
||||
anchors.fill: parent
|
||||
color: "transparent" // Initial color, will be set by parent
|
||||
|
||||
// Smooth transitions when warmth changes
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 300 }
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update overlay color
|
||||
function updateColor(newWarmth) {
|
||||
overlayRect.color = Qt.rgba(1.0, 0.8 - newWarmth * 0.4, 0.3 - newWarmth * 0.25, 0.1 + newWarmth * 0.2)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
try {
|
||||
overlayWindow = Qt.createQmlObject(qmlString, root)
|
||||
// Set initial color
|
||||
updateOverlay()
|
||||
} catch (e) {
|
||||
console.error("Failed to create night light overlay:", e)
|
||||
}
|
||||
}
|
||||
|
||||
function updateOverlay() {
|
||||
if (overlayWindow && overlayWindow.updateColor) {
|
||||
overlayWindow.updateColor(warmth)
|
||||
}
|
||||
}
|
||||
|
||||
function removeOverlay() {
|
||||
if (overlayWindow) {
|
||||
overlayWindow.destroy()
|
||||
overlayWindow = null
|
||||
}
|
||||
}
|
||||
|
||||
// Preset warmth levels for easy access
|
||||
function setLow() { Data.Settings.nightLightWarmth = 0.2 } // Light warmth
|
||||
function setMedium() { Data.Settings.nightLightWarmth = 0.4 } // Medium warmth
|
||||
function setHigh() { Data.Settings.nightLightWarmth = 0.6 } // High warmth
|
||||
function setMax() { Data.Settings.nightLightWarmth = 1.0 } // Maximum warmth
|
||||
|
||||
// Auto-enable based on time (basic sunset/sunrise simulation)
|
||||
Timer {
|
||||
interval: 60000 // Check every minute
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: checkAutoEnable()
|
||||
}
|
||||
|
||||
function checkAutoEnable() {
|
||||
if (!Data.Settings.nightLightAuto) return
|
||||
|
||||
var now = new Date()
|
||||
var hour = now.getHours()
|
||||
var minute = now.getMinutes()
|
||||
var startHour = Data.Settings.nightLightStartHour || 20
|
||||
var endHour = Data.Settings.nightLightEndHour || 6
|
||||
|
||||
// Handle overnight schedules (e.g., 20:00 to 6:00)
|
||||
var shouldBeActive = false
|
||||
if (startHour > endHour) {
|
||||
// Overnight: active from startHour onwards OR before endHour
|
||||
shouldBeActive = (hour >= startHour || hour < endHour)
|
||||
} else if (startHour < endHour) {
|
||||
// Same day: active between startHour and endHour
|
||||
shouldBeActive = (hour >= startHour && hour < endHour)
|
||||
} else {
|
||||
// startHour === endHour: never auto-enable
|
||||
shouldBeActive = false
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
console.log(`Night Light Auto Check: ${hour}:${minute.toString().padStart(2, '0')} - Should be active: ${shouldBeActive}, Currently active: ${Data.Settings.nightLightEnabled}, Manual override: ${Data.Settings.nightLightManualOverride}`)
|
||||
|
||||
// Smart override logic - only block conflicting actions
|
||||
if (Data.Settings.nightLightManualOverride) {
|
||||
// If user manually enabled, allow auto-disable but block auto-enable
|
||||
if (Data.Settings.nightLightManuallyEnabled && !shouldBeActive && Data.Settings.nightLightEnabled) {
|
||||
console.log("Auto-disabling night light (respecting schedule after manual enable)")
|
||||
autoSchedulerActive = true
|
||||
Data.Settings.nightLightEnabled = false
|
||||
Data.Settings.nightLightManualOverride = false // Reset after respecting schedule
|
||||
autoSchedulerActive = false
|
||||
return
|
||||
}
|
||||
// If user manually disabled, block auto-enable until next cycle
|
||||
else if (!Data.Settings.nightLightManuallyEnabled && shouldBeActive && !Data.Settings.nightLightEnabled) {
|
||||
// Check if this is the start of a new schedule cycle
|
||||
var isNewCycle = (hour === startHour && minute === 0)
|
||||
if (isNewCycle) {
|
||||
console.log("New schedule cycle starting - resetting manual override")
|
||||
Data.Settings.nightLightManualOverride = false
|
||||
} else {
|
||||
console.log("Manual disable override active - skipping auto-enable")
|
||||
return
|
||||
}
|
||||
}
|
||||
// Other cases - reset override and continue
|
||||
else {
|
||||
Data.Settings.nightLightManualOverride = false
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-enable when schedule starts
|
||||
if (shouldBeActive && !Data.Settings.nightLightEnabled) {
|
||||
console.log("Auto-enabling night light")
|
||||
autoSchedulerActive = true
|
||||
Data.Settings.nightLightEnabled = true
|
||||
autoSchedulerActive = false
|
||||
}
|
||||
// Auto-disable when schedule ends
|
||||
else if (!shouldBeActive && Data.Settings.nightLightEnabled) {
|
||||
console.log("Auto-disabling night light")
|
||||
autoSchedulerActive = true
|
||||
Data.Settings.nightLightEnabled = false
|
||||
autoSchedulerActive = false
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup on destruction
|
||||
Component.onDestruction: {
|
||||
removeOverlay()
|
||||
}
|
||||
|
||||
// Initialize overlay state based on settings
|
||||
Component.onCompleted: {
|
||||
if (Data.Settings.nightLightEnabled) {
|
||||
createOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Screen recording toggle button
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var shell
|
||||
required property bool isRecording
|
||||
radius: 20
|
||||
|
||||
signal recordingRequested()
|
||||
signal stopRecordingRequested()
|
||||
signal mouseChanged(bool containsMouse)
|
||||
|
||||
// Dynamic color: accent when recording/hovered, gray otherwise
|
||||
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
|
||||
|
||||
// Button content with icon and text
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
// Recording state icon
|
||||
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
|
||||
}
|
||||
|
||||
// Recording state label
|
||||
Label {
|
||||
text: isRecording ? "Stop Recording" : "Start Recording"
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Medium
|
||||
color: isRecording || mouseArea.containsMouse ? "#ffffff" : Data.ThemeManager.fgColor
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Click handling and hover detection
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onContainsMouseChanged: root.mouseChanged(containsMouse)
|
||||
|
||||
onClicked: {
|
||||
if (isRecording) {
|
||||
root.stopRecordingRequested()
|
||||
} else {
|
||||
root.recordingRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Simple theme toggle button with hover feedback
|
||||
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()
|
||||
|
||||
// Hover state management for parent components
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else if (!menuJustOpened) {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: themeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
Data.ThemeManager.toggleTheme()
|
||||
}
|
||||
}
|
||||
|
||||
// Theme toggle icon with color feedback
|
||||
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,243 @@
|
|||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import "root:/Data/" as Data
|
||||
|
||||
// User profile card
|
||||
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
|
||||
|
||||
// Dynamic color based on hover and active states
|
||||
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
|
||||
|
||||
// Avatar
|
||||
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 // Hidden for masking
|
||||
asynchronous: true
|
||||
sourceSize.width: 48 // Memory optimization
|
||||
sourceSize.height: 48
|
||||
}
|
||||
|
||||
// Apply circular mask to avatar
|
||||
OpacityMask {
|
||||
anchors.fill: avatarImage
|
||||
source: avatarImage
|
||||
cached: true // Cache to reduce ShaderEffect issues
|
||||
maskSource: Rectangle {
|
||||
width: avatarImage.width
|
||||
height: avatarImage.height
|
||||
radius: 18 // Proportional to parent radius
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User information text
|
||||
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 ? "#ffffff" : 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
|
||||
}
|
||||
}
|
||||
|
||||
// Animated GIF with rounded corners
|
||||
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
|
||||
}
|
||||
|
||||
// Apply rounded corner mask to GIF
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
cached: true // Cache to reduce ShaderEffect issues
|
||||
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
|
||||
}
|
||||
|
||||
// Get current username
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get system uptime with parsing for readable format
|
||||
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 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 for different uptime formats
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update uptime every 5 minutes
|
||||
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,354 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "root:/Data" as Data
|
||||
|
||||
// Weather display widget
|
||||
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()
|
||||
|
||||
// Hover state management for parent components
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse) {
|
||||
entered()
|
||||
} else if (!menuJustOpened && !forecastPopup.visible) {
|
||||
exited()
|
||||
}
|
||||
}
|
||||
|
||||
// Maps WMO weather condition codes and text descriptions to Material Design icons
|
||||
function getWeatherIcon(condition) {
|
||||
if (!condition) return "light_mode"
|
||||
|
||||
const c = condition.toString()
|
||||
|
||||
// WMO weather interpretation codes to Material Design icons
|
||||
const iconMap = {
|
||||
"0": "light_mode", // Clear sky
|
||||
"1": "light_mode", // Mainly clear
|
||||
"2": "cloud", // Partly cloudy
|
||||
"3": "cloud", // Overcast
|
||||
"45": "foggy", // Fog
|
||||
"48": "foggy", // Depositing rime fog
|
||||
"51": "water_drop", // Light drizzle
|
||||
"53": "water_drop", // Moderate drizzle
|
||||
"55": "water_drop", // Dense drizzle
|
||||
"61": "water_drop", // Slight rain
|
||||
"63": "water_drop", // Moderate rain
|
||||
"65": "water_drop", // Heavy rain
|
||||
"71": "ac_unit", // Slight snow
|
||||
"73": "ac_unit", // Moderate snow
|
||||
"75": "ac_unit", // Heavy snow
|
||||
"80": "water_drop", // Slight rain showers
|
||||
"81": "water_drop", // Moderate rain showers
|
||||
"82": "water_drop", // Violent rain showers
|
||||
"95": "thunderstorm", // Thunderstorm
|
||||
"96": "thunderstorm", // Thunderstorm with light hail
|
||||
"99": "thunderstorm" // Thunderstorm with heavy hail
|
||||
}
|
||||
|
||||
if (iconMap[c]) return iconMap[c]
|
||||
|
||||
// Fallback text matching for non-WMO weather APIs
|
||||
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" // Unknown condition fallback
|
||||
}
|
||||
|
||||
// Hover trigger for forecast popup
|
||||
MouseArea {
|
||||
id: weatherMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
menuJustOpened = true
|
||||
forecastPopup.open()
|
||||
Qt.callLater(() => menuJustOpened = false)
|
||||
}
|
||||
onExited: {
|
||||
if (!forecastPopup.containsMouse && !menuJustOpened) {
|
||||
forecastPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compact weather display (icon and temperature)
|
||||
RowLayout {
|
||||
id: weatherLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
// Weather condition icon
|
||||
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
|
||||
}
|
||||
|
||||
// Current temperature
|
||||
Label {
|
||||
text: {
|
||||
if (shell.weatherLoading) return "Loading..."
|
||||
if (!shell.weatherData) return "No weather data"
|
||||
return shell.weatherData.currentTemp
|
||||
}
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forecast popup
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// Hover area for popup persistence
|
||||
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
|
||||
|
||||
// Current weather detailed view
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
// Large weather icon
|
||||
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
|
||||
|
||||
// Weather condition description
|
||||
Label {
|
||||
text: shell.weatherData ? shell.weatherData.currentCondition : ""
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 14
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Weather metrics: temperature, wind, direction
|
||||
RowLayout {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
|
||||
// Temperature metric
|
||||
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.family: "Roboto"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 12
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
// Wind speed metric
|
||||
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.family: "Roboto"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 12
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
// Wind direction metric
|
||||
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.family: "Roboto"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Section separator
|
||||
Rectangle {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "3-Day Forecast"
|
||||
color: Data.ThemeManager.accentColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
// Three-column forecast cards
|
||||
Row {
|
||||
spacing: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
Repeater {
|
||||
model: shell.weatherData ? shell.weatherData.forecast : []
|
||||
delegate: Column {
|
||||
width: (parent.width - 16) / 3
|
||||
spacing: 2
|
||||
|
||||
// Day name
|
||||
Label {
|
||||
text: modelData.dayName
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
// Weather icon
|
||||
Label {
|
||||
text: root.getWeatherIcon(modelData.condition)
|
||||
font.pixelSize: 16
|
||||
font.family: "Material Symbols Outlined"
|
||||
color: Data.ThemeManager.accentColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
// Temperature range
|
||||
Label {
|
||||
text: modelData.minTemp + "° - " + modelData.maxTemp + "°"
|
||||
color: Data.ThemeManager.fgColor
|
||||
font.family: "Roboto"
|
||||
font.pixelSize: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue