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