add crypto

This commit is contained in:
zack 2025-07-22 20:21:21 -04:00
parent 90cbe489f6
commit af6a3bce3e
Signed by: zoey
GPG key ID: 81FB9FECDD6A33E2
120 changed files with 24616 additions and 462 deletions

View file

@ -0,0 +1,107 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
import "." as Controls
// Dual-section control panel
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)
// Combined hover state from both sections
property bool containsMouse: performanceSection.containsMouse || systemSection.containsMouse
onContainsMouseChanged: mouseChanged(containsMouse)
// Performance controls section (left half)
Rectangle {
id: performanceSection
width: (parent.width - parent.spacing) / 2
height: parent.height
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
visible: true
// Hover tracking with coordination between background and content
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
}
}
}
Controls.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
}
}
}
}
// System controls section (right half)
Rectangle {
id: systemSection
width: (parent.width - parent.spacing) / 2
height: parent.height
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
visible: true
// Hover tracking with coordination between background and content
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
}
}
}
Controls.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
}
}
}
}
}

View file

@ -0,0 +1,127 @@
import QtQuick
import QtQuick.Controls
import Quickshell.Services.UPower
// Power profile controls
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 UPower service access with fallback checks
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 mode 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 mode 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 mode 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)
}
}
}
}
// Ensure UPower service initialization
Component.onCompleted: {
Qt.callLater(function() {
if (!root.upowerReady) {
console.warn("UPower service not ready - performance controls may not work correctly")
}
})
}
}

View file

@ -0,0 +1,122 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// System button
Rectangle {
id: root
required property var shell
required property string iconText
property string labelText: ""
property bool isActive: false
radius: 20
// Dynamic color based on active and hover states
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
// Smooth color transitions
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
// Hover scale animation
scale: isHovered ? 1.05 : 1.0
Behavior on scale {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
// Button content with icon and optional label
Column {
anchors.centerIn: parent
spacing: 2
// System action icon
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
}
}
}
// Optional text label
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
}
}
}
}
// Click and hover handling
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: root.mouseChanged(containsMouse)
onClicked: root.clicked()
}
}

View file

@ -0,0 +1,93 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
// System action buttons
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)
}
}
}
}

View file

@ -0,0 +1,666 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Services.Mpris
import "root:/Data" as Data
// Music player with MPRIS integration
Rectangle {
id: musicPlayer
property var shell
property var currentPlayer: null
property real currentPosition: 0
property int selectedPlayerIndex: 0
color: "transparent"
// Get all available players
function getAvailablePlayers() {
if (!Mpris.players || !Mpris.players.values) {
return []
}
let allPlayers = Mpris.players.values
let controllablePlayers = []
for (let i = 0; i < allPlayers.length; i++) {
let player = allPlayers[i]
if (player && player.canControl) {
controllablePlayers.push(player)
}
}
return controllablePlayers
}
// Find the active player (either selected or first available)
function findActivePlayer() {
let availablePlayers = getAvailablePlayers()
if (availablePlayers.length === 0) {
return null
}
// Auto-switch to playing player if enabled
if (Data.Settings.autoSwitchPlayer) {
for (let i = 0; i < availablePlayers.length; i++) {
if (availablePlayers[i].isPlaying) {
selectedPlayerIndex = i
return availablePlayers[i]
}
}
}
// Use selected player if valid, otherwise use first available
if (selectedPlayerIndex < availablePlayers.length) {
return availablePlayers[selectedPlayerIndex]
} else {
selectedPlayerIndex = 0
return availablePlayers[0]
}
}
// Update current player
function updateCurrentPlayer() {
let newPlayer = findActivePlayer()
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer
currentPosition = currentPlayer ? currentPlayer.position : 0
}
}
// Timer to update progress bar position
Timer {
id: positionTimer
interval: 1000
running: currentPlayer && currentPlayer.isPlaying
repeat: true
onTriggered: {
if (currentPlayer) {
currentPosition = currentPlayer.position
}
}
}
// Timer to check for auto-switching to playing players
Timer {
id: autoSwitchTimer
interval: 2000 // Check every 2 seconds
running: Data.Settings.autoSwitchPlayer
repeat: true
onTriggered: {
if (Data.Settings.autoSwitchPlayer) {
let availablePlayers = getAvailablePlayers()
for (let i = 0; i < availablePlayers.length; i++) {
if (availablePlayers[i].isPlaying && selectedPlayerIndex !== i) {
selectedPlayerIndex = i
updateCurrentPlayer()
updatePlayerList()
break
}
}
}
}
}
// Update player list for dropdown
function updatePlayerList() {
if (!playerComboBox) return
let availablePlayers = getAvailablePlayers()
let playerNames = availablePlayers.map(player => player.identity || "Unknown Player")
playerComboBox.model = playerNames
if (selectedPlayerIndex >= playerNames.length) {
selectedPlayerIndex = 0
}
playerComboBox.currentIndex = selectedPlayerIndex
}
// Monitor for player changes
Connections {
target: Mpris.players
function onValuesChanged() {
updatePlayerList()
updateCurrentPlayer()
}
function onRowsInserted() {
updatePlayerList()
updateCurrentPlayer()
}
function onRowsRemoved() {
updatePlayerList()
updateCurrentPlayer()
}
function onObjectInsertedPost() {
updatePlayerList()
updateCurrentPlayer()
}
function onObjectRemovedPost() {
updatePlayerList()
updateCurrentPlayer()
}
}
// Monitor for settings changes
Connections {
target: Data.Settings
function onAutoSwitchPlayerChanged() {
console.log("Auto-switch player setting changed to:", Data.Settings.autoSwitchPlayer)
updateCurrentPlayer()
}
function onAlwaysShowPlayerDropdownChanged() {
console.log("Always show dropdown setting changed to:", Data.Settings.alwaysShowPlayerDropdown)
// Dropdown visibility is automatically handled by the binding
}
}
Component.onCompleted: {
updatePlayerList()
updateCurrentPlayer()
}
Column {
anchors.fill: parent
spacing: 10
// No music player available state
Item {
width: parent.width
height: parent.height
visible: !currentPlayer
Column {
anchors.centerIn: parent
spacing: 16
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "music_note"
font.family: "Material Symbols Outlined"
font.pixelSize: 48
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: getAvailablePlayers().length > 0 ? "No controllable player selected" : "No music player detected"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.family: "Roboto"
font.pixelSize: 14
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: getAvailablePlayers().length > 0 ? "Select a player from the dropdown above" : "Start a music player to see controls"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
font.family: "Roboto"
font.pixelSize: 12
}
}
}
// Music player controls
Column {
width: parent.width
spacing: 12
visible: currentPlayer
// Player info and artwork
Rectangle {
width: parent.width
height: 130
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
Row {
anchors.fill: parent
anchors.margins: 16
spacing: 16
// Album artwork
Rectangle {
id: albumArtwork
width: 90
height: 90
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.3)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
Image {
id: albumArt
anchors.fill: parent
anchors.margins: 2
fillMode: Image.PreserveAspectCrop
smooth: true
source: currentPlayer ? (currentPlayer.trackArtUrl || "") : ""
visible: source.toString() !== ""
// Rounded corners using layer
layer.enabled: true
layer.effect: OpacityMask {
cached: true // Cache to reduce ShaderEffect issues
maskSource: Rectangle {
width: albumArt.width
height: albumArt.height
radius: 20
visible: false
}
}
}
// Fallback music icon
Text {
anchors.centerIn: parent
text: "album"
font.family: "Material Symbols Outlined"
font.pixelSize: 32
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
visible: !albumArt.visible
}
}
// Track info
Column {
width: parent.width - albumArtwork.width - parent.spacing
height: parent.height
spacing: 4
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : ""
color: Data.ThemeManager.fgColor
font.family: "Roboto"
font.pixelSize: 18
font.bold: true
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
}
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : ""
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.8)
font.family: "Roboto"
font.pixelSize: 18
elide: Text.ElideRight
}
Text {
width: parent.width
text: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : ""
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.family: "Roboto"
font.pixelSize: 15
elide: Text.ElideRight
}
}
}
}
// Interactive progress bar with seek functionality
Rectangle {
id: progressBarBackground
width: parent.width
height: 8
radius: 20
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.15)
property real progressRatio: currentPlayer && currentPlayer.length > 0 ?
(currentPosition / currentPlayer.length) : 0
Rectangle {
id: progressFill
width: progressBarBackground.progressRatio * parent.width
height: parent.height
radius: parent.radius
color: Data.ThemeManager.accentColor
Behavior on width {
NumberAnimation { duration: 200 }
}
}
// Interactive progress handle (circle)
Rectangle {
id: progressHandle
width: 16
height: 16
radius: 8
color: Data.ThemeManager.accentColor
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.3)
border.width: 1
x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2))
anchors.verticalCenter: parent.verticalCenter
visible: currentPlayer && currentPlayer.length > 0
scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
// Mouse area for seeking
MouseArea {
id: progressMouseArea
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.length > 0 && currentPlayer.canSeek
onClicked: function(mouse) {
if (currentPlayer && currentPlayer.length > 0) {
let ratio = mouse.x / width
let seekPosition = ratio * currentPlayer.length
currentPlayer.position = seekPosition
currentPosition = seekPosition
}
}
onPositionChanged: function(mouse) {
if (pressed && currentPlayer && currentPlayer.length > 0) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let seekPosition = ratio * currentPlayer.length
currentPlayer.position = seekPosition
currentPosition = seekPosition
}
}
}
}
// Player selection dropdown (conditional visibility)
Rectangle {
width: parent.width
height: 38
radius: 20
color: Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
visible: {
let playerCount = getAvailablePlayers().length
let alwaysShow = Data.Settings.alwaysShowPlayerDropdown
let shouldShow = alwaysShow || playerCount > 1
return shouldShow
}
Row {
anchors.fill: parent
anchors.margins: 6
anchors.leftMargin: 12
spacing: 8
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Player:"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.family: "Roboto"
font.pixelSize: 12
font.bold: true
}
ComboBox {
id: playerComboBox
anchors.verticalCenter: parent.verticalCenter
width: parent.width - parent.children[0].width - parent.spacing
height: 26
model: []
onActivated: function(index) {
selectedPlayerIndex = index
updateCurrentPlayer()
}
background: Rectangle {
color: Qt.darker(Data.ThemeManager.bgColor, 1.3)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2)
border.width: 1
radius: 20
}
contentItem: Text {
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 22
anchors.verticalCenter: parent.verticalCenter
text: playerComboBox.currentText || "No players"
color: Data.ThemeManager.fgColor
font.family: "Roboto"
font.pixelSize: 12
font.bold: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
indicator: Text {
anchors.right: parent.right
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
text: "expand_more"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
}
popup: Popup {
y: playerComboBox.height + 2
width: playerComboBox.width
implicitHeight: contentItem.implicitHeight + 4
background: Rectangle {
color: Qt.darker(Data.ThemeManager.bgColor, 1.2)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
radius: 20
}
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: playerComboBox.popup.visible ? playerComboBox.delegateModel : null
currentIndex: playerComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator { }
}
}
delegate: ItemDelegate {
width: playerComboBox.width
height: 28
background: Rectangle {
color: parent.hovered ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15) : "transparent"
radius: 20
}
contentItem: Text {
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
text: modelData || ""
color: Data.ThemeManager.fgColor
font.family: "Roboto"
font.pixelSize: 12
font.bold: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
// Media controls
Row {
width: parent.width
height: 35
spacing: 6
// Previous button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.2
height: parent.height
radius: height / 2
color: previousButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: previousButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canGoPrevious
onClicked: if (currentPlayer) currentPlayer.previous()
}
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: previousButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Play/Pause button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.3
height: parent.height
radius: height / 2
color: playButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Data.ThemeManager.accentColor
border.width: 2
MouseArea {
id: playButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && (currentPlayer.canPlay || currentPlayer.canPause)
onClicked: {
if (currentPlayer) {
if (currentPlayer.isPlaying) {
currentPlayer.pause()
} else {
currentPlayer.play()
}
}
}
}
Text {
anchors.centerIn: parent
text: currentPlayer && currentPlayer.isPlaying ? "pause" : "play_arrow"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: playButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Next button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.2
height: parent.height
radius: height / 2
color: nextButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: nextButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canGoNext
onClicked: if (currentPlayer) currentPlayer.next()
}
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: "Material Symbols Outlined"
font.pixelSize: 18
color: nextButton.enabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Shuffle button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.15
height: parent.height
radius: height / 2
color: shuffleButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: currentPlayer && currentPlayer.shuffle ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: shuffleButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canControl && currentPlayer.shuffleSupported
onClicked: {
if (currentPlayer && currentPlayer.shuffleSupported) {
currentPlayer.shuffle = !currentPlayer.shuffle
}
}
}
Text {
anchors.centerIn: parent
text: "shuffle"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: shuffleButton.enabled ?
(currentPlayer && currentPlayer.shuffle ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
// Repeat button
Rectangle {
width: (parent.width - parent.spacing * 4) * 0.15
height: parent.height
radius: height / 2
color: repeatButton.containsMouse ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) : Qt.darker(Data.ThemeManager.bgColor, 1.1)
border.color: currentPlayer && currentPlayer.loopState !== MprisLoopState.None ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: 1
MouseArea {
id: repeatButton
anchors.fill: parent
hoverEnabled: true
enabled: currentPlayer && currentPlayer.canControl && currentPlayer.loopSupported
onClicked: {
if (currentPlayer && currentPlayer.loopSupported) {
if (currentPlayer.loopState === MprisLoopState.None) {
currentPlayer.loopState = MprisLoopState.Track
} else if (currentPlayer.loopState === MprisLoopState.Track) {
currentPlayer.loopState = MprisLoopState.Playlist
} else {
currentPlayer.loopState = MprisLoopState.None
}
}
}
}
Text {
anchors.centerIn: parent
text: currentPlayer && currentPlayer.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
font.family: "Material Symbols Outlined"
font.pixelSize: 12
color: repeatButton.enabled ?
(currentPlayer && currentPlayer.loopState !== MprisLoopState.None ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
}
}
}
}
}
}

View file

@ -0,0 +1,125 @@
import QtQuick
import "../../tabs" as Tabs
// Tab container with sliding animation
Item {
id: tabContainer
// Properties from parent
required property var shell
required property bool isRecording
required property var triggerMouseArea
property int currentTab: 0
// Signals to forward
signal recordingRequested()
signal stopRecordingRequested()
signal systemActionRequested(string action)
signal performanceActionRequested(string action)
// Hover detection combining all tab hovers
property bool isHovered: {
const tabHovers = [
mainDashboard.isHovered,
true, // Calendar tab should stay open when active
true, // Clipboard tab should stay open when active
true, // Notification tab should stay open when active
true, // Wallpaper tab should stay open when active
true, // Music tab should stay open when active
true // Settings tab should stay open when active
]
return tabHovers[currentTab] || false
}
// Track when text inputs have focus for keyboard management
property bool textInputFocused: currentTab === 6 && settingsTab.anyTextInputFocused
clip: true
// Sliding content container
Row {
id: slidingRow
width: parent.width * 7 // 7 tabs wide
height: parent.height
spacing: 0
// Animate horizontal position based on current tab
x: -tabContainer.currentTab * tabContainer.width
Behavior on x {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
// Tab 0: Main Dashboard
Tabs.MainDashboard {
id: mainDashboard
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isRecording: tabContainer.isRecording
triggerMouseArea: tabContainer.triggerMouseArea
onRecordingRequested: tabContainer.recordingRequested()
onStopRecordingRequested: tabContainer.stopRecordingRequested()
onSystemActionRequested: function(action) { tabContainer.systemActionRequested(action) }
onPerformanceActionRequested: function(action) { tabContainer.performanceActionRequested(action) }
}
// Tab 1: Calendar
Tabs.CalendarTab {
id: calendarTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 1 || Math.abs(tabContainer.currentTab - 1) <= 1
}
// Tab 2: Clipboard
Tabs.ClipboardTab {
id: clipboardTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 2 || Math.abs(tabContainer.currentTab - 2) <= 1
}
// Tab 3: Notifications
Tabs.NotificationTab {
id: notificationTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 3 || Math.abs(tabContainer.currentTab - 3) <= 1
}
// Tab 4: Wallpapers
Tabs.WallpaperTab {
id: wallpaperTab
width: tabContainer.width
height: parent.height
isActive: tabContainer.currentTab === 4 || Math.abs(tabContainer.currentTab - 4) <= 1
}
// Tab 5: Music
Tabs.MusicTab {
id: musicTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 5 || Math.abs(tabContainer.currentTab - 5) <= 1
}
// Tab 6: Settings
Tabs.SettingsTab {
id: settingsTab
width: tabContainer.width
height: parent.height
shell: tabContainer.shell
isActive: tabContainer.currentTab === 6 || Math.abs(tabContainer.currentTab - 6) <= 1
}
}
}

View file

@ -0,0 +1,132 @@
import QtQuick
import "root:/Data" as Data
// Tab navigation sidebar
Item {
id: tabNavigation
property int currentTab: 0
property var tabIcons: []
property bool containsMouse: sidebarMouseArea.containsMouse || tabColumn.containsMouse
MouseArea {
id: sidebarMouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
}
// Tab button background - matches system controls
Rectangle {
width: 38
height: tabColumn.height + 12
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
radius: 19
border.width: 1
border.color: Qt.lighter(Data.ThemeManager.bgColor, 1.3)
// Subtle inner shadow effect
Rectangle {
anchors.fill: parent
anchors.margins: 1
color: Qt.darker(Data.ThemeManager.bgColor, 1.05)
radius: parent.radius - 1
opacity: 0.3
}
}
// Tab icon buttons
Column {
id: tabColumn
spacing: 6
anchors.top: parent.top
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
property bool containsMouse: {
for (let i = 0; i < tabRepeater.count; i++) {
const tab = tabRepeater.itemAt(i)
if (tab && tab.mouseArea && tab.mouseArea.containsMouse) {
return true
}
}
return false
}
Repeater {
id: tabRepeater
model: 7
delegate: Rectangle {
width: 30
height: 30
radius: 15
// Dynamic background based on state
color: {
if (tabNavigation.currentTab === index) {
return Data.ThemeManager.accentColor
} else if (tabMouseArea.containsMouse) {
return Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
} else {
return "transparent"
}
}
// Subtle shadow for active tab
Rectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
border.width: tabNavigation.currentTab === index ? 0 : (tabMouseArea.containsMouse ? 1 : 0)
visible: tabNavigation.currentTab !== index
}
property alias mouseArea: tabMouseArea
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
tabNavigation.currentTab = index
}
}
Text {
anchors.centerIn: parent
text: tabNavigation.tabIcons[index] || ""
font.family: "Material Symbols Outlined"
font.pixelSize: 16
color: {
if (tabNavigation.currentTab === index) {
return Data.ThemeManager.bgColor
} else if (tabMouseArea.containsMouse) {
return Data.ThemeManager.accentColor
} else {
return Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
}
}
// Smooth color transitions
Behavior on color {
ColorAnimation { duration: 150 }
}
}
// Smooth transitions
Behavior on color {
ColorAnimation { duration: 150 }
}
// Subtle scale effect on hover
scale: tabMouseArea.containsMouse ? 1.05 : 1.0
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
}
}
}
}
}

View file

@ -0,0 +1,622 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
// Appearance settings content
Column {
width: parent.width
spacing: 20
// Theme Setting in Collapsible Section
SettingsCategory {
width: parent.width
title: "Theme Setting"
icon: "palette"
content: Component {
Column {
width: parent.width
spacing: 30 // Increased spacing between major sections
// Dark/Light Mode Switch
Column {
width: parent.width
spacing: 12
Text {
text: "Theme Mode"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "Roboto"
}
Row {
spacing: 16
anchors.horizontalCenter: parent.horizontalCenter
Text {
text: "Light"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
}
// Toggle switch - enhanced design
Rectangle {
width: 64
height: 32
radius: 16
color: Data.ThemeManager.currentTheme.type === "dark" ?
Qt.lighter(Data.ThemeManager.accentColor, 0.8) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 2
border.color: Data.ThemeManager.currentTheme.type === "dark" ?
Data.ThemeManager.accentColor :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
anchors.verticalCenter: parent.verticalCenter
// Inner track shadow
Rectangle {
anchors.fill: parent
anchors.margins: 2
radius: parent.radius - 2
color: "transparent"
border.width: 1
border.color: Qt.rgba(0, 0, 0, 0.1)
}
// Toggle handle
Rectangle {
id: toggleHandle
width: 26
height: 26
radius: 13
color: Data.ThemeManager.currentTheme.type === "dark" ?
Data.ThemeManager.bgColor : Data.ThemeManager.panelBackground
border.width: 2
border.color: Data.ThemeManager.currentTheme.type === "dark" ?
Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.ThemeManager.currentTheme.type === "dark" ? parent.width - width - 3 : 3
// Handle shadow
Rectangle {
anchors.centerIn: parent
anchors.verticalCenterOffset: 1
width: parent.width - 2
height: parent.height - 2
radius: parent.radius - 1
color: Qt.rgba(0, 0, 0, 0.1)
z: -1
}
// Handle highlight
Rectangle {
anchors.centerIn: parent
width: parent.width - 6
height: parent.height - 6
radius: parent.radius - 3
color: Qt.rgba(255, 255, 255, 0.15)
}
Behavior on x {
NumberAnimation {
duration: 250
easing.type: Easing.OutBack
easing.overshoot: 0.3
}
}
Behavior on border.color {
ColorAnimation { duration: 200 }
}
}
// Background color transition
Behavior on color {
ColorAnimation { duration: 200 }
}
Behavior on border.color {
ColorAnimation { duration: 200 }
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
console.log("Theme switch clicked, current:", Data.ThemeManager.currentThemeId)
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "")
var newType = Data.ThemeManager.currentTheme.type === "dark" ? "light" : "dark"
var newThemeId = currentFamily + "_" + newType
console.log("Switching to:", newThemeId)
Data.ThemeManager.setTheme(newThemeId)
// Force update the settings if currentTheme isn't being saved properly
if (!Data.Settings.currentTheme) {
Data.Settings.currentTheme = newThemeId
Data.Settings.saveSettings()
}
}
onEntered: {
parent.scale = 1.05
}
onExited: {
parent.scale = 1.0
}
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
}
Text {
text: "Dark"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Separator
Rectangle {
width: parent.width - 40
height: 1
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter
}
// Theme Selection
Column {
width: parent.width
spacing: 12
Text {
text: "Theme Family"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "Roboto"
}
Text {
text: "Choose your preferred theme family"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
anchors.horizontalCenter: parent.horizontalCenter
}
// Compact 2x2 grid for themes
GridLayout {
columns: 2
columnSpacing: 8
rowSpacing: 8
anchors.horizontalCenter: parent.horizontalCenter
property var themeFamily: {
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "")
return currentFamily
}
property var themeFamilies: [
{ id: "oxocarbon", name: "Oxocarbon", description: "IBM Carbon" },
{ id: "dracula", name: "Dracula", description: "Vibrant" },
{ id: "gruvbox", name: "Gruvbox", description: "Retro" },
{ id: "catppuccin", name: "Catppuccin", description: "Pastel" },
{ id: "matugen", name: "Matugen", description: "Generated" }
]
Repeater {
model: parent.themeFamilies
delegate: Rectangle {
Layout.preferredWidth: 140
Layout.preferredHeight: 50
radius: 10
color: parent.themeFamily === modelData.id ?
Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: parent.themeFamily === modelData.id ? 2 : 1
border.color: parent.themeFamily === modelData.id ?
Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
spacing: 6
// Compact theme preview colors
Row {
spacing: 1
property var previewTheme: Data.ThemeManager.themes[modelData.id + "_" + Data.ThemeManager.currentTheme.type] || Data.ThemeManager.themes[modelData.id + "_dark"]
Rectangle { width: 4; height: 14; radius: 1; color: parent.previewTheme.base00 }
Rectangle { width: 4; height: 14; radius: 1; color: parent.previewTheme.base0E }
Rectangle { width: 4; height: 14; radius: 1; color: parent.previewTheme.base0D }
Rectangle { width: 4; height: 14; radius: 1; color: parent.previewTheme.base0B }
}
Column {
spacing: 1
anchors.verticalCenter: parent.verticalCenter
Text {
text: modelData.name
color: parent.parent.parent.parent.themeFamily === modelData.id ?
Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 12
font.bold: parent.parent.parent.parent.themeFamily === modelData.id
font.family: "Roboto"
}
Text {
text: modelData.description
color: parent.parent.parent.parent.themeFamily === modelData.id ?
Qt.rgba(Data.ThemeManager.bgColor.r, Data.ThemeManager.bgColor.g, Data.ThemeManager.bgColor.b, 0.8) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 9
font.family: "Roboto"
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
var themeType = Data.ThemeManager.currentTheme.type
var newThemeId = modelData.id + "_" + themeType
console.log("Theme card clicked:", newThemeId)
Data.ThemeManager.setTheme(newThemeId)
}
onEntered: {
parent.scale = 1.02
}
onExited: {
parent.scale = 1.0
}
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
}
}
}
}
// Separator
Rectangle {
width: parent.width - 40
height: 1
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1)
anchors.horizontalCenter: parent.horizontalCenter
}
// Accent Colors
Column {
width: parent.width
spacing: 12
Text {
text: "Accent Colors"
color: Data.ThemeManager.fgColor
font.pixelSize: 15
font.bold: true
font.family: "Roboto"
}
Text {
text: "Choose your preferred accent color for " + Data.ThemeManager.currentTheme.name
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
}
// Compact flow layout for accent colors
Flow {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 20 // Margins to prevent clipping
spacing: 8
property var accentColors: {
var currentFamily = Data.ThemeManager.currentThemeId.replace(/_dark$|_light$/, "")
var themeColors = []
// Theme-specific accent colors - reduced to 5 per theme for compactness
if (currentFamily === "dracula") {
themeColors.push(
{ name: "Magenta", dark: "#ff79c6", light: "#e91e63" },
{ name: "Purple", dark: "#bd93f9", light: "#6c7ce0" },
{ name: "Cyan", dark: "#8be9fd", light: "#17a2b8" },
{ name: "Green", dark: "#50fa7b", light: "#27ae60" },
{ name: "Orange", dark: "#ffb86c", light: "#f39c12" }
)
} else if (currentFamily === "gruvbox") {
themeColors.push(
{ name: "Orange", dark: "#fe8019", light: "#d65d0e" },
{ name: "Red", dark: "#fb4934", light: "#cc241d" },
{ name: "Yellow", dark: "#fabd2f", light: "#d79921" },
{ name: "Green", dark: "#b8bb26", light: "#98971a" },
{ name: "Purple", dark: "#d3869b", light: "#b16286" }
)
} else if (currentFamily === "catppuccin") {
themeColors.push(
{ name: "Mauve", dark: "#cba6f7", light: "#8839ef" },
{ name: "Blue", dark: "#89b4fa", light: "#1e66f5" },
{ name: "Teal", dark: "#94e2d5", light: "#179299" },
{ name: "Green", dark: "#a6e3a1", light: "#40a02b" },
{ name: "Peach", dark: "#fab387", light: "#fe640b" }
)
} else if (currentFamily === "matugen") {
// Use dynamic matugen colors if available
if (Data.ThemeManager.matugen && Data.ThemeManager.matugen.isMatugenActive()) {
themeColors.push(
{ name: "Primary", dark: Data.ThemeManager.matugen.getMatugenColor("primary") || "#adc6ff", light: Data.ThemeManager.matugen.getMatugenColor("primary") || "#0f62fe" },
{ name: "Secondary", dark: Data.ThemeManager.matugen.getMatugenColor("secondary") || "#bfc6dc", light: Data.ThemeManager.matugen.getMatugenColor("secondary") || "#6272a4" },
{ name: "Tertiary", dark: Data.ThemeManager.matugen.getMatugenColor("tertiary") || "#debcdf", light: Data.ThemeManager.matugen.getMatugenColor("tertiary") || "#b16286" },
{ name: "Surface", dark: Data.ThemeManager.matugen.getMatugenColor("surface_tint") || "#adc6ff", light: Data.ThemeManager.matugen.getMatugenColor("surface_tint") || "#0f62fe" },
{ name: "Error", dark: Data.ThemeManager.matugen.getMatugenColor("error") || "#ffb4ab", light: Data.ThemeManager.matugen.getMatugenColor("error") || "#ba1a1a" }
)
} else {
// Fallback matugen colors
themeColors.push(
{ name: "Primary", dark: "#adc6ff", light: "#0f62fe" },
{ name: "Secondary", dark: "#bfc6dc", light: "#6272a4" },
{ name: "Tertiary", dark: "#debcdf", light: "#b16286" },
{ name: "Surface", dark: "#adc6ff", light: "#0f62fe" },
{ name: "Error", dark: "#ffb4ab", light: "#ba1a1a" }
)
}
} else { // oxocarbon and fallback
themeColors.push(
{ name: "Purple", dark: "#be95ff", light: "#8a3ffc" },
{ name: "Blue", dark: "#78a9ff", light: "#0f62fe" },
{ name: "Cyan", dark: "#3ddbd9", light: "#007d79" },
{ name: "Green", dark: "#42be65", light: "#198038" },
{ name: "Pink", dark: "#ff7eb6", light: "#d12771" }
)
}
return themeColors
}
Repeater {
model: parent.accentColors
delegate: Rectangle {
width: 60
height: 50
radius: 10
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: Data.ThemeManager.accentColor.toString() === (Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light) ? 3 : 1
border.color: Data.ThemeManager.accentColor.toString() === (Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light) ?
Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Column {
anchors.centerIn: parent
spacing: 4
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.currentTheme.type === "dark" ? modelData.dark : modelData.light
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: modelData.name
color: Data.ThemeManager.fgColor
font.pixelSize: 9
font.family: "Roboto"
font.bold: true
anchors.horizontalCenter: parent.horizontalCenter
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
// Set custom accent
Data.Settings.useCustomAccent = true
Data.ThemeManager.setCustomAccent(modelData.dark, modelData.light)
}
onEntered: {
parent.scale = 1.05
}
onExited: {
parent.scale = 1.0
}
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
}
}
}
}
}
}
}
// Animation Settings in Collapsible Section
SettingsCategory {
width: parent.width
title: "Animation Settings"
icon: "animation"
content: Component {
Column {
width: parent.width
spacing: 20
Text {
text: "Configure workspace change animations"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
anchors.horizontalCenter: parent.horizontalCenter
}
// Workspace Burst Toggle
Row {
width: parent.width
height: 40
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 80
Text {
text: "Workspace Burst Effect"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
}
Text {
text: "Expanding rings when switching workspaces"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 11
font.family: "Roboto"
}
}
// Toggle switch for burst
Rectangle {
width: 50
height: 25
radius: 12.5
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
color: Data.Settings.workspaceBurstEnabled ?
Qt.lighter(Data.ThemeManager.accentColor, 0.8) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 1
border.color: Data.Settings.workspaceBurstEnabled ?
Data.ThemeManager.accentColor :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
border.width: 1.5
border.color: Data.Settings.workspaceBurstEnabled ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.Settings.workspaceBurstEnabled ? parent.width - width - 2.5 : 2.5
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
Behavior on color { ColorAnimation { duration: 200 } }
Behavior on border.color { ColorAnimation { duration: 200 } }
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.workspaceBurstEnabled = !Data.Settings.workspaceBurstEnabled
}
}
}
}
// Workspace Glow Toggle
Row {
width: parent.width
height: 40
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 80
Text {
text: "Workspace Shadow Glow"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
}
Text {
text: "Accent color glow in workspace shadow"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 11
font.family: "Roboto"
}
}
// Toggle switch for glow
Rectangle {
width: 50
height: 25
radius: 12.5
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
color: Data.Settings.workspaceGlowEnabled ?
Qt.lighter(Data.ThemeManager.accentColor, 0.8) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
border.width: 1
border.color: Data.Settings.workspaceGlowEnabled ?
Data.ThemeManager.accentColor :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.4)
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
border.width: 1.5
border.color: Data.Settings.workspaceGlowEnabled ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
anchors.verticalCenter: parent.verticalCenter
x: Data.Settings.workspaceGlowEnabled ? parent.width - width - 2.5 : 2.5
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
Behavior on color { ColorAnimation { duration: 200 } }
Behavior on border.color { ColorAnimation { duration: 200 } }
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.workspaceGlowEnabled = !Data.Settings.workspaceGlowEnabled
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,121 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Music Player settings content
Column {
width: parent.width
spacing: 20
// Auto-switch to active player
Column {
width: parent.width
spacing: 12
Text {
text: "Auto-switch to Active Player"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Automatically switch to the player that starts playing music"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
Rectangle {
width: 200
height: 35
radius: 18
color: Data.Settings.autoSwitchPlayer ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation { duration: 200 }
}
Text {
anchors.centerIn: parent
text: Data.Settings.autoSwitchPlayer ? "Enabled" : "Disabled"
color: Data.Settings.autoSwitchPlayer ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.autoSwitchPlayer = !Data.Settings.autoSwitchPlayer
}
}
}
}
// Always show player dropdown
Column {
width: parent.width
spacing: 12
Text {
text: "Always Show Player Dropdown"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Show the player selection dropdown even with only one player"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
Rectangle {
width: 200
height: 35
radius: 18
color: Data.Settings.alwaysShowPlayerDropdown ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation { duration: 200 }
}
Text {
anchors.centerIn: parent
text: Data.Settings.alwaysShowPlayerDropdown ? "Enabled" : "Disabled"
color: Data.Settings.alwaysShowPlayerDropdown ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.alwaysShowPlayerDropdown = !Data.Settings.alwaysShowPlayerDropdown
}
}
}
}
}

View file

@ -0,0 +1,517 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
// Night Light settings content
Item {
id: nightLightSettings
width: parent.width
height: contentColumn.height
Column {
id: contentColumn
width: parent.width
spacing: 20
// Night Light Enable Toggle
Row {
width: parent.width
spacing: 16
Column {
width: parent.width - nightLightToggle.width - 16
spacing: 4
Text {
text: "Enable Night Light"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Reduces blue light to help protect your eyes and improve sleep"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
}
}
Rectangle {
id: nightLightToggle
width: 50
height: 28
radius: 14
color: Data.Settings.nightLightEnabled ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on color {
ColorAnimation { duration: 200 }
}
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
x: Data.Settings.nightLightEnabled ? parent.width - width - 4 : 4
anchors.verticalCenter: parent.verticalCenter
Behavior on x {
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
Data.Settings.nightLightEnabled = !Data.Settings.nightLightEnabled
}
onEntered: {
parent.scale = 1.05
}
onExited: {
parent.scale = 1.0
}
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
}
}
// Warmth Level Slider
Column {
width: parent.width
spacing: 12
Text {
text: "Warmth Level"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Adjust how warm the screen filter appears"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
Row {
width: parent.width
spacing: 12
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Cool"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 12
font.family: "Roboto"
}
Slider {
id: warmthSlider
width: parent.width - 120
height: 30
from: 0.1
to: 1.0
value: Data.Settings.nightLightWarmth || 0.4
stepSize: 0.1
onValueChanged: {
Data.Settings.nightLightWarmth = value
}
background: Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 6
radius: 3
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.2)
Rectangle {
width: warmthSlider.visualPosition * parent.width
height: parent.height
radius: parent.radius
color: Qt.rgba(1.0, 0.8 - warmthSlider.value * 0.3, 0.4, 1.0)
}
}
handle: Rectangle {
x: warmthSlider.leftPadding + warmthSlider.visualPosition * (warmthSlider.availableWidth - width)
y: warmthSlider.topPadding + warmthSlider.availableHeight / 2 - height / 2
width: 20
height: 20
radius: 10
color: Data.ThemeManager.accentColor
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
border.width: 2
}
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Warm"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.6)
font.pixelSize: 12
font.family: "Roboto"
}
}
}
// Auto-enable Toggle
Row {
width: parent.width
spacing: 16
Column {
width: parent.width - autoToggle.width - 16
spacing: 4
Text {
text: "Auto-enable Schedule"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Automatically turn on night light at sunset/bedtime"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
}
}
Rectangle {
id: autoToggle
width: 50
height: 28
radius: 14
color: Data.Settings.nightLightAuto ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on color {
ColorAnimation { duration: 200 }
}
Rectangle {
width: 20
height: 20
radius: 10
color: Data.ThemeManager.bgColor
x: Data.Settings.nightLightAuto ? parent.width - width - 4 : 4
anchors.verticalCenter: parent.verticalCenter
Behavior on x {
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
Data.Settings.nightLightAuto = !Data.Settings.nightLightAuto
}
onEntered: {
parent.scale = 1.05
}
onExited: {
parent.scale = 1.0
}
}
Behavior on scale {
NumberAnimation { duration: 150; easing.type: Easing.OutQuad }
}
}
}
// Schedule Time Controls - visible when auto-enable is on
Column {
width: parent.width
spacing: 16
visible: Data.Settings.nightLightAuto
opacity: Data.Settings.nightLightAuto ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation { duration: 200 }
}
Text {
text: "Schedule Times"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
// Start and End Time Row
Row {
width: parent.width
spacing: 20
// Start Time
Column {
id: startTimeColumn
width: (parent.width - parent.spacing) / 2
spacing: 8
Text {
text: "Start Time"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
}
Text {
text: "Night light turns on"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 12
font.family: "Roboto"
}
Rectangle {
id: startTimeButton
width: parent.width
height: 40
radius: 8
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Row {
anchors.centerIn: parent
spacing: 8
Text {
text: (Data.Settings.nightLightStartHour || 20).toString().padStart(2, '0') + ":00"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
onClicked: {
startTimePopup.open()
}
}
}
// Start Time Popup
Popup {
id: startTimePopup
width: startTimeButton.width
height: 170
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
y: startTimeButton.y - height - 10
x: startTimeButton.x
dim: false
background: Rectangle {
color: Data.ThemeManager.bgColor
radius: 12
border.width: 2
border.color: Data.ThemeManager.accentColor
}
Column {
anchors.centerIn: parent
spacing: 12
Text {
text: "Select Start Hour"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
anchors.horizontalCenter: parent.horizontalCenter
}
GridLayout {
columns: 6
columnSpacing: 6
rowSpacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: 24
delegate: Rectangle {
width: 24
height: 24
radius: 4
color: (Data.Settings.nightLightStartHour || 20) === modelData ?
Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Text {
anchors.centerIn: parent
text: modelData.toString().padStart(2, '0')
color: (Data.Settings.nightLightStartHour || 20) === modelData ?
Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 10
font.bold: true
font.family: "Roboto"
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.nightLightStartHour = modelData
startTimePopup.close()
}
}
}
}
}
}
}
}
// End Time
Column {
id: endTimeColumn
width: (parent.width - parent.spacing) / 2
spacing: 8
Text {
text: "End Time"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
}
Text {
text: "Night light turns off"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 12
font.family: "Roboto"
}
Rectangle {
id: endTimeButton
width: parent.width
height: 40
radius: 8
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Row {
anchors.centerIn: parent
spacing: 8
Text {
text: (Data.Settings.nightLightEndHour || 6).toString().padStart(2, '0') + ":00"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
onClicked: {
endTimePopup.open()
}
}
}
// End Time Popup
Popup {
id: endTimePopup
width: endTimeButton.width
height: 170
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
y: endTimeButton.y - height - 10
x: endTimeButton.x
dim: false
background: Rectangle {
color: Data.ThemeManager.bgColor
radius: 12
border.width: 2
border.color: Data.ThemeManager.accentColor
}
Column {
anchors.centerIn: parent
spacing: 12
Text {
text: "Select End Hour"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
anchors.horizontalCenter: parent.horizontalCenter
}
GridLayout {
columns: 6
columnSpacing: 6
rowSpacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
model: 24
delegate: Rectangle {
width: 24
height: 24
radius: 4
color: (Data.Settings.nightLightEndHour || 6) === modelData ?
Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Text {
anchors.centerIn: parent
text: modelData.toString().padStart(2, '0')
color: (Data.Settings.nightLightEndHour || 6) === modelData ?
Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 10
font.bold: true
font.family: "Roboto"
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.nightLightEndHour = modelData
endTimePopup.close()
}
}
}
}
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,531 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Notification settings content
Item {
id: notificationSettings
width: parent.width
height: contentColumn.height
// Expose the text input focus for parent keyboard management
property bool anyTextInputFocused: appNameInput.activeFocus
Column {
id: contentColumn
width: parent.width
spacing: 20
// Display Time Setting
Column {
width: parent.width
spacing: 12
Text {
text: "Display Time"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "How long notifications stay visible on screen"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
Row {
spacing: 16
width: parent.width
Slider {
id: displayTimeSlider
width: parent.width - timeLabel.width - 16
height: 30
from: 2000
to: 15000
stepSize: 1000
value: Data.Settings.displayTime
onValueChanged: {
Data.Settings.displayTime = value
}
background: Rectangle {
width: displayTimeSlider.availableWidth
height: 6
radius: 3
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: displayTimeSlider.visualPosition * parent.width
height: parent.height
radius: parent.radius
color: Data.ThemeManager.accentColor
}
}
handle: Rectangle {
x: displayTimeSlider.leftPadding + displayTimeSlider.visualPosition * (displayTimeSlider.availableWidth - width)
y: displayTimeSlider.topPadding + displayTimeSlider.availableHeight / 2 - height / 2
width: 20
height: 20
radius: 10
color: Data.ThemeManager.accentColor
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
border.width: 2
scale: displayTimeSlider.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
}
Text {
id: timeLabel
text: (displayTimeSlider.value / 1000).toFixed(1) + "s"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
width: 40
}
}
}
// Max History Items
Column {
width: parent.width
spacing: 12
Text {
text: "History Limit"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Maximum number of notifications to keep in history"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
Row {
spacing: 16
width: parent.width
Slider {
id: historySlider
width: parent.width - historyLabel.width - 16
height: 30
from: 10
to: 100
stepSize: 5
value: Data.Settings.historyLimit
onValueChanged: {
Data.Settings.historyLimit = value
}
background: Rectangle {
width: historySlider.availableWidth
height: 6
radius: 3
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: historySlider.visualPosition * parent.width
height: parent.height
radius: parent.radius
color: Data.ThemeManager.accentColor
}
}
handle: Rectangle {
x: historySlider.leftPadding + historySlider.visualPosition * (historySlider.availableWidth - width)
y: historySlider.topPadding + historySlider.availableHeight / 2 - height / 2
width: 20
height: 20
radius: 10
color: Data.ThemeManager.accentColor
border.color: Qt.lighter(Data.ThemeManager.accentColor, 1.2)
border.width: 2
scale: historySlider.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
}
Text {
id: historyLabel
text: historySlider.value + " items"
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
anchors.verticalCenter: parent.verticalCenter
width: 60
}
}
}
// Ignored Apps Setting
Column {
width: parent.width
spacing: 12
Text {
text: "Ignored Applications"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Text {
text: "Applications that won't show notifications"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 13
font.family: "Roboto"
wrapMode: Text.Wrap
width: parent.width
}
// Current ignored apps list
Rectangle {
width: parent.width
height: Math.max(100, ignoredAppsFlow.height + 16)
radius: 12
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Flow {
id: ignoredAppsFlow
anchors.fill: parent
anchors.margins: 8
spacing: 6
Repeater {
model: Data.Settings.ignoredApps
delegate: Rectangle {
width: appNameText.width + removeButton.width + 16
height: 28
radius: 14
color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.3)
Row {
anchors.centerIn: parent
spacing: 4
Text {
id: appNameText
anchors.verticalCenter: parent.verticalCenter
text: modelData
color: Data.ThemeManager.fgColor
font.pixelSize: 12
font.family: "Roboto"
}
Rectangle {
id: removeButton
width: 18
height: 18
radius: 9
color: removeMouseArea.containsMouse ?
Qt.rgba(1, 0.3, 0.3, 0.8) : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.5)
Behavior on color {
ColorAnimation { duration: 150 }
}
Text {
anchors.centerIn: parent
text: "×"
color: "white"
font.pixelSize: 12
font.bold: true
}
MouseArea {
id: removeMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
Data.Settings.removeIgnoredApp(modelData)
}
}
}
}
}
}
// Add new app button
Rectangle {
width: addAppText.width + 36
height: 28
radius: 14
color: addAppMouseArea.containsMouse ?
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) :
Qt.lighter(Data.ThemeManager.bgColor, 1.2)
border.width: 2
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation { duration: 150 }
}
Row {
anchors.centerIn: parent
spacing: 6
Text {
anchors.verticalCenter: parent.verticalCenter
text: "add"
font.family: "Material Symbols Outlined"
font.pixelSize: 14
color: Data.ThemeManager.accentColor
}
Text {
id: addAppText
anchors.verticalCenter: parent.verticalCenter
text: "Add App"
color: Data.ThemeManager.accentColor
font.pixelSize: 12
font.bold: true
font.family: "Roboto"
}
}
MouseArea {
id: addAppMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: addAppPopup.open()
}
}
}
}
// Quick suggestions
Column {
width: parent.width
spacing: 8
Text {
text: "Common Apps"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 12
font.family: "Roboto"
}
Flow {
width: parent.width
spacing: 6
Repeater {
model: ["Discord", "Spotify", "Steam", "Firefox", "Chrome", "VSCode", "Slack"]
delegate: Rectangle {
width: suggestedAppText.width + 16
height: 24
radius: 12
color: suggestionMouseArea.containsMouse ?
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.1) :
"transparent"
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Text {
id: suggestedAppText
anchors.centerIn: parent
text: modelData
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.7)
font.pixelSize: 11
font.family: "Roboto"
}
MouseArea {
id: suggestionMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
Data.Settings.addIgnoredApp(modelData)
}
}
}
}
}
}
}
}
// Add app popup
Popup {
id: addAppPopup
parent: notificationSettings
width: 280
height: 160
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Data.ThemeManager.bgColor
border.color: Data.ThemeManager.accentColor
border.width: 2
radius: 20
}
Column {
anchors.centerIn: parent
spacing: 16
width: parent.width - 40
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Add Ignored App"
color: Data.ThemeManager.accentColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Rectangle {
width: parent.width
height: 40
radius: 20
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: appNameInput.activeFocus ? 2 : 1
border.color: appNameInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on border.color {
ColorAnimation { duration: 150 }
}
TextInput {
id: appNameInput
anchors.fill: parent
anchors.margins: 12
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
selectByMouse: true
clip: true
verticalAlignment: TextInput.AlignVCenter
focus: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
addAppButton.clicked()
event.accepted = true
}
}
// Placeholder text implementation
Text {
anchors.fill: parent
anchors.margins: 12
text: "App name (e.g. Discord)"
color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.5)
font.pixelSize: 14
font.family: "Roboto"
verticalAlignment: Text.AlignVCenter
visible: appNameInput.text === ""
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 12
Rectangle {
width: 80
height: 32
radius: 16
color: cancelMouseArea.containsMouse ? Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.1) : "transparent"
border.width: 1
border.color: Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Text {
anchors.centerIn: parent
text: "Cancel"
color: Data.ThemeManager.fgColor
font.pixelSize: 12
font.family: "Roboto"
}
MouseArea {
id: cancelMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
appNameInput.text = ""
addAppPopup.close()
}
}
}
Rectangle {
id: addAppButton
width: 80
height: 32
radius: 16
color: addMouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.1) : Data.ThemeManager.accentColor
signal clicked()
onClicked: {
if (appNameInput.text.trim() !== "") {
if (Data.Settings.addIgnoredApp(appNameInput.text.trim())) {
appNameInput.text = ""
addAppPopup.close()
}
}
}
Text {
anchors.centerIn: parent
text: "Add"
color: Data.ThemeManager.bgColor
font.pixelSize: 12
font.bold: true
font.family: "Roboto"
}
MouseArea {
id: addMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: parent.clicked()
}
}
}
}
onOpened: {
appNameInput.forceActiveFocus()
}
}
}

View file

@ -0,0 +1,104 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Reusable collapsible settings category component
Item {
id: categoryRoot
property string title: ""
property string icon: ""
property bool expanded: false
property alias content: contentLoader.sourceComponent
height: headerRect.height + (expanded ? contentLoader.height + 20 : 0)
Behavior on height {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
// Category header
Rectangle {
id: headerRect
width: parent.width
height: 50
radius: 12
color: expanded ? Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.1) :
Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: expanded ? 2 : 1
border.color: expanded ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 16
spacing: 12
Text {
anchors.verticalCenter: parent.verticalCenter
text: categoryRoot.icon
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: categoryRoot.title
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
}
// Expand/collapse arrow
Text {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 16
text: expanded ? "expand_less" : "expand_more"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: expanded ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
Behavior on rotation {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
categoryRoot.expanded = !categoryRoot.expanded
}
}
}
// Category content
Loader {
id: contentLoader
anchors.top: headerRect.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: expanded ? 20 : 0
anchors.leftMargin: 16
anchors.rightMargin: 16
visible: expanded
opacity: expanded ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 250
easing.type: Easing.OutCubic
}
}
}
}

View file

@ -0,0 +1,131 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// System settings content
Item {
id: systemSettings
width: parent.width
height: contentColumn.height
// Expose the text input focus for parent keyboard management
property bool anyTextInputFocused: videoPathInput.activeFocus || wallpaperDirectoryInput.activeFocus
Column {
id: contentColumn
width: parent.width
spacing: 20
// Video Recording Path
Column {
width: parent.width
spacing: 8
Text {
text: "Video Recording Path"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Rectangle {
width: parent.width
height: 40
radius: 8
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: videoPathInput.activeFocus ? 2 : 1
border.color: videoPathInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on border.color {
ColorAnimation { duration: 150 }
}
TextInput {
id: videoPathInput
anchors.fill: parent
anchors.margins: 12
text: Data.Settings.videoPath
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
selectByMouse: true
clip: true
verticalAlignment: TextInput.AlignVCenter
focus: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
onTextChanged: {
Data.Settings.videoPath = text
}
Keys.onPressed: function(event) {
// Allow default text input behavior
}
}
MouseArea {
anchors.fill: parent
onClicked: {
videoPathInput.forceActiveFocus()
}
}
}
}
// Wallpaper Directory
Column {
width: parent.width
spacing: 8
Text {
text: "Wallpaper Directory"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Rectangle {
width: parent.width
height: 40
radius: 8
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: wallpaperDirectoryInput.activeFocus ? 2 : 1
border.color: wallpaperDirectoryInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on border.color {
ColorAnimation { duration: 150 }
}
TextInput {
id: wallpaperDirectoryInput
anchors.fill: parent
anchors.margins: 12
text: Data.Settings.wallpaperDirectory
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
selectByMouse: true
clip: true
verticalAlignment: TextInput.AlignVCenter
focus: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
onTextChanged: {
Data.Settings.wallpaperDirectory = text
}
MouseArea {
anchors.fill: parent
onClicked: {
wallpaperDirectoryInput.forceActiveFocus()
}
}
}
}
}
}
}

View file

@ -0,0 +1,197 @@
import QtQuick
import QtQuick.Controls
import "root:/Data" as Data
// Weather settings content
Item {
id: weatherSettings
width: parent.width
height: contentColumn.height
required property var shell
// Expose the text input focus for parent keyboard management
property bool anyTextInputFocused: locationInput.activeFocus
Column {
id: contentColumn
width: parent.width
spacing: 20
// Location Setting
Column {
width: parent.width
spacing: 8
Text {
text: "Location"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Row {
width: parent.width
spacing: 12
Rectangle {
width: parent.width - applyButton.width - 12
height: 40
radius: 8
color: Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: locationInput.activeFocus ? 2 : 1
border.color: locationInput.activeFocus ? Data.ThemeManager.accentColor : Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.3)
Behavior on border.color {
ColorAnimation { duration: 150 }
}
TextInput {
id: locationInput
anchors.fill: parent
anchors.margins: 12
text: Data.Settings.weatherLocation
color: Data.ThemeManager.fgColor
font.pixelSize: 14
font.family: "Roboto"
selectByMouse: true
clip: true
verticalAlignment: TextInput.AlignVCenter
focus: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
applyButton.clicked()
event.accepted = true
}
}
MouseArea {
anchors.fill: parent
onClicked: {
locationInput.forceActiveFocus()
}
}
}
}
Rectangle {
id: applyButton
width: 80
height: 40
radius: 8
color: applyMouseArea.containsMouse ? Qt.lighter(Data.ThemeManager.accentColor, 1.1) : Data.ThemeManager.accentColor
signal clicked()
onClicked: {
Data.Settings.weatherLocation = locationInput.text
weatherSettings.shell.weatherService.loadWeather()
}
Text {
anchors.centerIn: parent
text: "Apply"
color: Data.ThemeManager.bgColor
font.pixelSize: 12
font.bold: true
font.family: "Roboto"
}
MouseArea {
id: applyMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: parent.clicked()
}
}
}
}
// Temperature Units
Column {
width: parent.width
spacing: 12
Text {
text: "Temperature Units"
color: Data.ThemeManager.fgColor
font.pixelSize: 16
font.bold: true
font.family: "Roboto"
}
Row {
spacing: 12
Rectangle {
width: 80
height: 35
radius: 18
color: !Data.Settings.useFahrenheit ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation { duration: 200 }
}
Text {
anchors.centerIn: parent
text: "°C"
color: !Data.Settings.useFahrenheit ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.useFahrenheit = false
}
}
}
Rectangle {
width: 80
height: 35
radius: 18
color: Data.Settings.useFahrenheit ? Data.ThemeManager.accentColor : Qt.lighter(Data.ThemeManager.bgColor, 1.15)
border.width: 1
border.color: Data.ThemeManager.accentColor
Behavior on color {
ColorAnimation { duration: 200 }
}
Text {
anchors.centerIn: parent
text: "°F"
color: Data.Settings.useFahrenheit ? Data.ThemeManager.bgColor : Data.ThemeManager.fgColor
font.pixelSize: 14
font.bold: true
font.family: "Roboto"
Behavior on color {
ColorAnimation { duration: 200 }
}
}
MouseArea {
anchors.fill: parent
onClicked: {
Data.Settings.useFahrenheit = true
}
}
}
}
}
}
}

View file

@ -0,0 +1,98 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "root:/Data" as Data
// Dual-button notification and clipboard history toggle bar
Rectangle {
id: root
width: 42
color: Qt.darker(Data.ThemeManager.bgColor, 1.15)
radius: 12
z: 2 // Above notification history overlay
required property bool notificationHistoryVisible
required property bool clipboardHistoryVisible
required property var notificationHistory
signal notificationToggleRequested()
signal clipboardToggleRequested()
// Combined hover state for parent component tracking
property bool containsMouse: notifButtonMouseArea.containsMouse || clipButtonMouseArea.containsMouse
property real buttonHeight: 38
height: buttonHeight * 2 + 4 // Two buttons with spacing
Item {
anchors.fill: parent
anchors.margins: 2
// Notifications toggle button (top half)
Rectangle {
id: notificationPill
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: parent.verticalCenter
bottomMargin: 2 // Half of button spacing
}
radius: 12
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ?
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
border.color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
border.width: 1
MouseArea {
id: notifButtonMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.notificationToggleRequested()
}
Label {
anchors.centerIn: parent
text: "notifications"
font.family: "Material Symbols Outlined"
font.pixelSize: 16
color: notifButtonMouseArea.containsMouse || root.notificationHistoryVisible ?
Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
}
}
// Clipboard toggle button (bottom half)
Rectangle {
id: clipboardPill
anchors {
top: parent.verticalCenter
left: parent.left
right: parent.right
bottom: parent.bottom
topMargin: 2 // Half of button spacing
}
radius: 12
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ?
Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.2) :
Qt.rgba(Data.ThemeManager.fgColor.r, Data.ThemeManager.fgColor.g, Data.ThemeManager.fgColor.b, 0.05)
border.color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ? Data.ThemeManager.accentColor : "transparent"
border.width: 1
MouseArea {
id: clipButtonMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.clipboardToggleRequested()
}
Label {
anchors.centerIn: parent
text: "content_paste"
font.family: "Material Symbols Outlined"
font.pixelSize: 16
color: clipButtonMouseArea.containsMouse || root.clipboardHistoryVisible ?
Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
}
}
}
}

View file

@ -0,0 +1,52 @@
import QtQuick
// Top-edge hover trigger
Rectangle {
id: root
width: 360
height: 1
color: "red"
anchors.top: parent.top
signal triggered()
// Hover detection area at screen top edge
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
property bool isHovered: containsMouse
// Timer coordination
onIsHoveredChanged: {
if (isHovered) {
showTimer.start()
hideTimer.stop()
} else {
hideTimer.start()
showTimer.stop()
}
}
onEntered: hideTimer.stop()
}
// Delayed show trigger to prevent accidental activation
Timer {
id: showTimer
interval: 200
onTriggered: root.triggered()
}
// Hide delay timer (controlled by parent)
Timer {
id: hideTimer
interval: 500
}
// Public interface
readonly property alias containsMouse: mouseArea.containsMouse
function stopHideTimer() { hideTimer.stop() }
function startHideTimer() { hideTimer.start() }
}

View file

@ -0,0 +1,226 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import "root:/Data" as Data
// System tray context menu
Rectangle {
id: root
width: parent.width
height: visible ? calculatedHeight : 0
visible: false
enabled: visible
clip: true
color: Data.ThemeManager.bgColor
border.color: Data.ThemeManager.accentColor
border.width: 2
radius: 20
required property var menu
required property var systemTrayY
required property var systemTrayHeight
property bool containsMouse: trayMenuMouseArea.containsMouse
property bool menuJustOpened: false
property point triggerPoint: Qt.point(0, 0)
property Item originalParent
property int totalCount: opener.children ? opener.children.values.length : 0
signal hideRequested()
MouseArea {
id: trayMenuMouseArea
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
}
onVisibleChanged: {
if (visible) {
menuJustOpened = true
Qt.callLater(function() {
menuJustOpened = false
})
}
}
function toggle() {
visible = !visible
if (visible) {
menuJustOpened = true
Qt.callLater(function() {
menuJustOpened = false
})
}
}
function show(point, parentItem) {
visible = true
menuJustOpened = true
Qt.callLater(function() {
menuJustOpened = false
})
}
function hide() {
visible = false
menuJustOpened = false
// Small delay before notifying hide to prevent control panel flicker
Qt.callLater(function() {
hideRequested()
})
}
// Smart positioning to avoid screen edges
y: {
var preferredY = systemTrayY + systemTrayHeight + 10
var availableSpace = parent.height - preferredY - 20
if (calculatedHeight > availableSpace) {
return systemTrayY - height - 10
}
return preferredY
}
Behavior on height {
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
// Dynamic height calculation based on menu item count and types
property int calculatedHeight: {
if (totalCount === 0) return 40
var separatorCount = 0
var regularItemCount = 0
if (opener.children && opener.children.values) {
for (var i = 0; i < opener.children.values.length; i++) {
if (opener.children.values[i].isSeparator) {
separatorCount++
} else {
regularItemCount++
}
}
}
// Calculate total height: separators + grid rows + margins
var separatorHeight = separatorCount * 12
var regularItemRows = Math.ceil(regularItemCount / 2)
var regularItemHeight = regularItemRows * 32
return Math.max(80, 35 + separatorHeight + regularItemHeight + 40)
}
// Menu opener handles the native menu integration
QsMenuOpener {
id: opener
menu: root.menu
}
// Grid layout for menu items (2 columns)
GridView {
id: gridView
anchors.fill: parent
anchors.margins: 20
cellWidth: width / 2
cellHeight: 32
interactive: false
flow: GridView.FlowLeftToRight
layoutDirection: Qt.LeftToRight
model: ScriptModel {
values: opener.children ? [...opener.children.values] : []
}
delegate: Item {
id: entry
required property var modelData
required property int index
width: gridView.cellWidth - 4
height: modelData.isSeparator ? 12 : 30
// Separator line
Rectangle {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
anchors.topMargin: 4
anchors.bottomMargin: 4
visible: modelData.isSeparator
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width * 0.8
height: 1
color: Qt.darker(Data.ThemeManager.accentColor, 1.5)
opacity: 0.6
}
}
// Regular menu item
Rectangle {
id: itemBackground
anchors.fill: parent
anchors.margins: 2
visible: !modelData.isSeparator
color: "transparent"
radius: 6
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 6
Image {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
source: modelData?.icon ?? ""
visible: (modelData?.icon ?? "") !== ""
fillMode: Image.PreserveAspectFit
}
Text {
Layout.fillWidth: true
color: mouseArea.containsMouse ? Data.ThemeManager.accentColor : Data.ThemeManager.fgColor
text: modelData?.text ?? ""
font.pixelSize: 11
font.family: "Roboto"
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
maximumLineCount: 1
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
enabled: (modelData?.enabled ?? true) && root.visible && !modelData.isSeparator
onEntered: itemBackground.color = Qt.rgba(Data.ThemeManager.accentColor.r, Data.ThemeManager.accentColor.g, Data.ThemeManager.accentColor.b, 0.15)
onExited: itemBackground.color = "transparent"
onClicked: {
modelData.triggered()
root.hide()
}
}
}
}
}
// Empty state indicator
Item {
anchors.centerIn: gridView
visible: gridView.count === 0
Label {
anchors.centerIn: parent
text: "No tray items available"
color: Qt.darker(Data.ThemeManager.fgColor, 2)
font.pixelSize: 14
font.family: "Roboto"
}
}
}

View file

@ -0,0 +1,149 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import "root:/Data" as Data
// Wallpaper selector grid
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
// Hover state management for auto-hide functionality
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
}
// Scrollable wallpaper grid with memory-conscious loading
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-column layout with spacing
cellHeight: cellWidth * 0.6 // Wallpaper aspect ratio
model: Data.WallpaperManager.wallpaperList
cacheBuffer: 0 // Memory optimization - no cache buffer
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 }
}
// Wallpaper preview image with viewport-based loading
Image {
id: wallpaperImage
anchors.fill: parent
anchors.margins: 4
source: modelData
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false // Memory optimization - no image caching
sourceSize.width: Math.min(width, 150) // Reduced resolution for memory
sourceSize.height: Math.min(height, 90)
// Only load when visible in viewport - major memory optimization
visible: parent.parent.y >= wallpaperGrid.contentY - parent.parent.height &&
parent.parent.y <= wallpaperGrid.contentY + wallpaperGrid.height
// Layer effects disabled for memory savings
// layer.enabled: true
// layer.effect: OpacityMask {
// maskSource: Rectangle {
// width: wallpaperImage.width
// height: wallpaperImage.height
// radius: 18
// }
// }
}
// Current wallpaper selection indicator
Rectangle {
visible: modelData === Data.WallpaperManager.currentWallpaper
anchors.fill: parent
radius: parent.radius
color: "transparent"
border.color: Data.ThemeManager.accentColor
border.width: 2
}
// Hover and click handling
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: wallpaperItem.scale = 1.05
onExited: wallpaperItem.scale = 1.0
onClicked: {
Data.WallpaperManager.setWallpaper(modelData)
// Stays in wallpaper tab after selection
}
}
}
}
}
}
Component.onCompleted: {
// Use lazy loading to only load wallpapers when this component is actually used
Data.WallpaperManager.ensureWallpapersLoaded()
}
}

View file

@ -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)
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}
}
}
}
}