Possible ram improvement, move settings into sidebar

This commit is contained in:
Ly-sec 2025-08-06 16:02:33 +02:00
parent 61e852ed51
commit 3f6bc3414d
17 changed files with 795 additions and 2454 deletions

View file

@ -102,6 +102,7 @@ Scope {
}
NotificationIcon {
shell: rootScope.shell
anchors.verticalCenter: parent.verticalCenter
}
@ -135,12 +136,9 @@ Scope {
anchors.verticalCenter: parent.verticalCenter
}
SettingsButton {
anchors.verticalCenter: parent.verticalCenter
}
PanelPopup {
id: sidebarPopup
shell: rootScope.shell
}
Button {
@ -151,8 +149,7 @@ Scope {
}
}
Background {}
Overview {}
}
PanelWindow {

View file

@ -55,9 +55,13 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
bluetoothMenu.visible = !bluetoothMenu.visible;
if (!bluetoothMenuLoader.active) {
bluetoothMenuLoader.loading = true;
}
if (bluetoothMenuLoader.item) {
bluetoothMenuLoader.item.visible = !bluetoothMenuLoader.item.visible;
// Enable adapter and start discovery when menu opens
if (bluetoothMenu.visible && Bluetooth.defaultAdapter) {
if (bluetoothMenuLoader.item.visible && Bluetooth.defaultAdapter) {
if (!Bluetooth.defaultAdapter.enabled) {
Bluetooth.defaultAdapter.enabled = true;
}
@ -66,6 +70,7 @@ Item {
}
}
}
}
onEntered: bluetoothTooltip.tooltipVisible = true
onExited: bluetoothTooltip.tooltipVisible = false
}
@ -80,7 +85,11 @@ Item {
delay: 200
}
PanelWindow {
// LazyLoader for Bluetooth menu
LazyLoader {
id: bluetoothMenuLoader
loading: false
component: PanelWindow {
id: bluetoothMenu
implicitWidth: 320
implicitHeight: 480
@ -270,3 +279,4 @@ Item {
}
}
}
}

View file

@ -1,80 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
import qs.Components
import qs.Widgets.SettingsWindow
Item {
id: root
width: 22
height: 22
property var settingsWindow: null
Rectangle {
id: button
anchors.fill: parent
color: "transparent"
radius: width / 2
Text {
anchors.centerIn: parent
text: "settings"
font.family: "Material Symbols Outlined"
font.pixelSize: 16
color: mouseArea.containsMouse ? Theme.accentPrimary : Theme.textPrimary
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!settingsWindow) {
// Create new window
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
if (settingsWindow) {
settingsWindow.visible = true;
// Handle window closure
settingsWindow.visibleChanged.connect(function() {
if (settingsWindow && !settingsWindow.visible) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
});
}
} else if (settingsWindow.visible) {
// Close and destroy window
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.visible = false;
windowToDestroy.destroy();
}
}
}
StyledTooltip {
text: "Settings"
targetItem: mouseArea
tooltipVisible: mouseArea.containsMouse
}
}
Component {
id: settingsComponent
SettingsWindow {}
}
// Clean up on destruction
Component.onDestruction: {
if (settingsWindow) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
}
}

View file

@ -61,13 +61,18 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
wifiMenu.visible = !wifiMenu.visible;
if (wifiMenu.visible) {
if (!wifiMenuLoader.active) {
wifiMenuLoader.loading = true;
}
if (wifiMenuLoader.item) {
wifiMenuLoader.item.visible = !wifiMenuLoader.item.visible;
if (wifiMenuLoader.item.visible) {
network.onMenuOpened();
} else {
network.onMenuClosed();
}
}
}
onEntered: wifiTooltip.tooltipVisible = true
onExited: wifiTooltip.tooltipVisible = false
}
@ -82,7 +87,11 @@ Item {
delay: 200
}
PanelWindow {
// LazyLoader for WiFi menu
LazyLoader {
id: wifiMenuLoader
loading: false
component: PanelWindow {
id: wifiMenu
implicitWidth: 320
implicitHeight: 480
@ -368,3 +377,4 @@ Item {
}
}
}
}

View file

@ -16,33 +16,33 @@ Shape {
property var modelData: null
// Position flags derived from position string
property bool _isTop: position.includes("top")
property bool _isLeft: position.includes("left")
property bool _isRight: position.includes("right")
property bool _isBottom: position.includes("bottom")
// Position flags derived from position string - calculated once
readonly property bool _isTop: position.includes("top")
readonly property bool _isLeft: position.includes("left")
readonly property bool _isRight: position.includes("right")
readonly property bool _isBottom: position.includes("bottom")
// Shift the path vertically if offsetY is negative to pull shape up
property real pathOffsetY: Math.min(offsetY, 0)
readonly property real pathOffsetY: Math.min(offsetY, 0)
// Base coordinates for left corner shape, shifted by pathOffsetY vertically
property real _baseStartX: 30 * size
property real _baseStartY: (_isTop ? 20 * size : 0) + pathOffsetY
property real _baseLineX: 30 * size
property real _baseLineY: (_isTop ? 0 : 20 * size) + pathOffsetY
property real _baseArcX: 50 * size
property real _baseArcY: (_isTop ? 20 * size : 0) + pathOffsetY
readonly property real _baseStartX: 30 * size
readonly property real _baseStartY: (_isTop ? 20 * size : 0) + pathOffsetY
readonly property real _baseLineX: 30 * size
readonly property real _baseLineY: (_isTop ? 0 : 20 * size) + pathOffsetY
readonly property real _baseArcX: 50 * size
readonly property real _baseArcY: (_isTop ? 20 * size : 0) + pathOffsetY
// Mirror coordinates for right corners
property real _startX: _isRight ? (concaveWidth - _baseStartX) : _baseStartX
property real _startY: _baseStartY
property real _lineX: _isRight ? (concaveWidth - _baseLineX) : _baseLineX
property real _lineY: _baseLineY
property real _arcX: _isRight ? (concaveWidth - _baseArcX) : _baseArcX
property real _arcY: _baseArcY
readonly property real _startX: _isRight ? (concaveWidth - _baseStartX) : _baseStartX
readonly property real _startY: _baseStartY
readonly property real _lineX: _isRight ? (concaveWidth - _baseLineX) : _baseLineX
readonly property real _lineY: _baseLineY
readonly property real _arcX: _isRight ? (concaveWidth - _baseArcX) : _baseArcX
readonly property real _arcY: _baseArcY
// Arc direction varies by corner to maintain proper concave shape
property int _arcDirection: {
readonly property int _arcDirection: {
if (_isTop && _isLeft) return PathArc.Counterclockwise
if (_isTop && _isRight) return PathArc.Clockwise
if (_isBottom && _isLeft) return PathArc.Clockwise
@ -57,9 +57,10 @@ Shape {
x: _isLeft ? offsetX : (parent ? parent.width - width + offsetX : 0)
y: _isTop ? offsetY : (parent ? parent.height - height + offsetY : 0)
preferredRendererType: Shape.CurveRenderer
layer.enabled: true
layer.samples: 4
// Optimized rendering settings - reduced quality for better performance
preferredRendererType: Shape.GeometryRenderer // Use simpler renderer
layer.enabled: false // Disable layer rendering to save memory
antialiasing: true // Use standard antialiasing instead of MSAA
ShapePath {
strokeWidth: 0

View file

@ -14,7 +14,6 @@ IpcHandler {
idleInhibitor.toggle()
}
function toggleNotificationPopup(): void {
console.log("[IPC] NotificationPopup toggle() called")
// Use the global toggle function from the notification manager

View file

@ -8,13 +8,25 @@ Item {
id: root
width: 22; height: 22
property bool isSilence: false
property var shell: null
Process {
id: rightClickProcess
command: ["qs","ipc", "call", "globalIPC", "toggleNotificationPopup"]
}
// Timer to check when NotificationHistory is loaded
Timer {
id: checkHistoryTimer
interval: 50
repeat: true
onTriggered: {
if (shell && shell.notificationHistoryWin) {
shell.notificationHistoryWin.visible = true;
checkHistoryTimer.stop();
}
}
}
Item {
id: bell
@ -22,11 +34,23 @@ Item {
Text {
id: bellText
anchors.centerIn: parent
text: notificationHistoryWin.hasUnread ? "notifications_unread" : "notifications"
text: {
if (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread) {
return "notifications_unread";
} else {
return "notifications";
}
}
font.family: mouseAreaBell.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
font.pixelSize: 16
font.weight: notificationHistoryWin.hasUnread ? Font.Bold : Font.Normal
color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (notificationHistoryWin.hasUnread ? Theme.accentPrimary : Theme.textDisabled)
font.weight: {
if (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread) {
return Font.Bold;
} else {
return Font.Normal;
}
}
color: mouseAreaBell.containsMouse ? Theme.accentPrimary : (shell && shell.notificationHistoryWin && shell.notificationHistoryWin.hasUnread ? Theme.accentPrimary : Theme.textDisabled)
}
MouseArea {
id: mouseAreaBell
@ -42,10 +66,18 @@ Item {
}
if (mouse.button === Qt.LeftButton){
notificationHistoryWin.visible = !notificationHistoryWin.visible
if (shell) {
if (!shell.notificationHistoryWin) {
// Use the shell function to load notification history
shell.loadNotificationHistory();
checkHistoryTimer.start();
} else {
// Already loaded, just toggle visibility
shell.notificationHistoryWin.visible = !shell.notificationHistoryWin.visible;
}
}
return;
}
}
onEntered: notificationTooltip.tooltipVisible = true
onExited: notificationTooltip.tooltipVisible = false

View file

@ -10,14 +10,32 @@ import qs.Widgets.SettingsWindow.Tabs.Components
PanelWindow {
id: panelMain
implicitHeight: screen.height / 2
implicitWidth: screen.width / 2
implicitHeight: Quickshell.screens.length > 0 ? Quickshell.screens[0].height / 2 : 400
implicitWidth: Quickshell.screens.length > 0 ? Quickshell.screens[0].width / 2 : 600
color: "transparent"
property int activeTabIndex: 0
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
// Add safety checks for component loading
Component.onCompleted: {
// Ensure we start with a valid tab
if (activeTabIndex < 0 || activeTabIndex > 8) {
activeTabIndex = 0;
}
}
// Cleanup when window is hidden
onVisibleChanged: {
if (!visible) {
// Reset to default tab when hiding to prevent state issues
activeTabIndex = 0;
if (tabName) {
tabName.text = "General";
}
}
}
Component {
id: generalSettings
@ -84,7 +102,6 @@ PanelWindow {
}
}
Rectangle {
id: settings
color: Theme.backgroundPrimary
@ -98,7 +115,6 @@ PanelWindow {
topRightRadius: 20
bottomRightRadius: 20
Rectangle {
id: headerArea
anchors {
@ -114,7 +130,6 @@ PanelWindow {
anchors.fill: parent
spacing: 12
Text {
id: tabName
text: wallpaperSelector.visible ? "Select Wallpaper" : (activeTabIndex === 0 ? "General" :
@ -189,7 +204,6 @@ PanelWindow {
}
}
Rectangle {
anchors {
top: headerArea.bottom
@ -213,35 +227,11 @@ PanelWindow {
topMargin: 32
}
// Simplified single loader approach
Loader {
id: settingsLoader
anchors.fill: parent
sourceComponent: generalSettings
opacity: 1
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
}
Loader {
id: settingsLoader2
anchors.fill: parent
opacity: 0
visible: opacity > 0
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutQuad
}
}
}
// Wallpaper Selector Component
@ -252,11 +242,10 @@ PanelWindow {
}
}
Rectangle {
id: tabs
color: Theme.surface
width: screen.width / 9
width: Quickshell.screens.length > 0 ? Quickshell.screens[0].width / 9 : 100
height: panelMain.height
topLeftRadius: 20
bottomLeftRadius: 20
@ -356,7 +345,6 @@ PanelWindow {
8: aboutSettings
}[index];
const tabNames = [
"General",
"Bar",
@ -370,21 +358,10 @@ PanelWindow {
];
tabName.text = tabNames[index];
if (settingsLoader.opacity === 1) {
settingsLoader2.sourceComponent = newComponent;
settingsLoader.opacity = 0;
settingsLoader2.opacity = 1;
} else {
// Simple component switching
if (newComponent) {
settingsLoader.sourceComponent = newComponent;
settingsLoader2.opacity = 0;
settingsLoader.opacity = 1;
}
// Tab colors and indicators are now handled by property bindings
}
}

View file

@ -1,56 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import qs.Settings
ColumnLayout {
property alias title: headerText.text
property bool expanded: false // Hidden by default
default property alias content: contentItem.children
Rectangle {
Layout.fillWidth: true
height: 44
radius: 12
color: Theme.surface
border.color: Theme.accentPrimary
border.width: 2
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 8
Item { width: 2 }
Text {
id: headerText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeBody
font.bold: true
color: Theme.textPrimary
}
Item { Layout.fillWidth: true }
Rectangle {
width: 32; height: 32
color: "transparent"
Text {
anchors.centerIn: parent
text: expanded ? "expand_less" : "expand_more"
font.family: "Material Symbols Outlined"
font.pixelSize: Theme.fontSizeBody
color: Theme.accentPrimary
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: expanded = !expanded
}
}
Item { height: 8 }
ColumnLayout {
id: contentItem
Layout.fillWidth: true
visible: expanded
spacing: 0
}
}

View file

@ -1,643 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import QtQuick.Controls
import Quickshell.Widgets
import qs.Components
import qs.Settings
Rectangle {
id: profileSettingsCard
Layout.fillWidth: true
Layout.preferredHeight: 690
color: Theme.surface
radius: 18
ColumnLayout {
anchors.fill: parent
anchors.margins: 18
spacing: 12
// Header
RowLayout {
Layout.fillWidth: true
spacing: 12
Text {
text: "settings"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Theme.accentPrimary
}
Text {
text: "Profile Settings"
font.family: Theme.fontFamily
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.fillWidth: true
}
}
// Profile Image Input Section
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "Profile Image"
font.family: Theme.fontFamily
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
RowLayout {
spacing: 8
Layout.fillWidth: true
// Profile image
Rectangle {
width: 48
height: 48
radius: 24
// Border
Rectangle {
anchors.fill: parent
color: "transparent"
radius: 24
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 2
z: 2
}
Avatar {}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: profileImageInput
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.profileImage !== undefined ? Settings.settings.profileImage : ""
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhUrlCharactersOnly
onTextChanged: {
Settings.settings.profileImage = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: profileImageInput.forceActiveFocus()
}
}
}
}
}
// Show Active Window Icon Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Active Window Icon"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: activeWindowIconSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showActiveWindowIcon ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showActiveWindowIcon ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: activeWindowIconThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showActiveWindowIcon ? activeWindowIconSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showActiveWindowIcon = !Settings.settings.showActiveWindowIcon;
}
}
}
}
// Show System Info In Bar Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show System Info In Bar"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: systemInfoSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showSystemInfoInBar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showSystemInfoInBar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: systemInfoThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showSystemInfoInBar ? systemInfoSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showSystemInfoInBar = !Settings.settings.showSystemInfoInBar;
}
}
}
}
// Show Corners Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Corners"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: cornersSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showCorners ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showCorners ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: cornersThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showCorners ? cornersSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showCorners = !Settings.settings.showCorners;
}
}
}
}
// Show Taskbar Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Taskbar"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: taskbarSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showTaskbar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showTaskbar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: taskbarThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showTaskbar ? taskbarSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showTaskbar = !Settings.settings.showTaskbar;
}
}
}
}
// Show Dock Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Dock"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: dockSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showDock ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showDock ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: dockThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showDock ? taskbarSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showDock = !Settings.settings.showDock;
}
}
}
}
// Show Media In Bar Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Show Media In Bar"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: mediaSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.showMediaInBar ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.showMediaInBar ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: mediaThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.showMediaInBar ? mediaSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.showMediaInBar = !Settings.settings.showMediaInBar;
}
}
}
}
// Dim Windows Setting
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Dim Desktop"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Rectangle {
id: dimSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.dimPanels ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.dimPanels ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: dimThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.dimPanels ? dimSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.dimPanels = !Settings.settings.dimPanels;
}
}
}
}
// Visualizer Type Selection
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 16
Text {
text: "Visualizer Type"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
ComboBox {
id: visualizerTypeComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["radial", "fire", "diamond"]
currentIndex: model.indexOf(Settings.settings.visualizerType)
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: visualizerTypeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: visualizerTypeComboBox.indicator.width + visualizerTypeComboBox.spacing
text: visualizerTypeComboBox.displayText.charAt(0).toUpperCase() + visualizerTypeComboBox.displayText.slice(1)
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: visualizerTypeComboBox.width - width - 12
y: visualizerTypeComboBox.topPadding + (visualizerTypeComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: visualizerTypeComboBox.height
width: visualizerTypeComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: visualizerTypeComboBox.popup.visible ? visualizerTypeComboBox.delegateModel : null
currentIndex: visualizerTypeComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: visualizerTypeComboBox.width
contentItem: Text {
text: modelData.charAt(0).toUpperCase() + modelData.slice(1)
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
highlighted: visualizerTypeComboBox.highlightedIndex === index
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
onActivated: {
Settings.settings.visualizerType = model[index];
}
}
}
// Video Path Input Section
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 16
Text {
text: "Video Path"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: videoPathInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: videoPathInput
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.videoPath !== undefined ? Settings.settings.videoPath : ""
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhUrlCharactersOnly
onTextChanged: {
Settings.settings.videoPath = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: videoPathInput.forceActiveFocus()
}
}
}
}
}
}

View file

@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Settings
import qs.Services
import qs.Widgets.SettingsWindow
import qs.Components
PanelWindow {
@ -19,164 +20,55 @@ PanelWindow {
margins.top: 0
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
Rectangle {
anchors.fill: parent
color: Theme.backgroundPrimary
radius: 20
z: 0
ColumnLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 32
anchors.rightMargin: 32
anchors.topMargin: 32
spacing: 24
// Header
ColumnLayout {
id: header
Layout.fillWidth: true
spacing: 4
RowLayout {
Layout.fillWidth: true
spacing: 20
Text {
text: "settings"
font.family: "Material Symbols Outlined"
font.pixelSize: 32
color: Theme.accentPrimary
}
Text {
text: "Settings"
font.pixelSize: 26
font.bold: true
color: Theme.textPrimary
Layout.fillWidth: true
}
Rectangle {
width: 36
height: 36
radius: 18
color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent"
border.color: Theme.accentPrimary
border.width: 1
Text {
anchors.centerIn: parent
text: "close"
font.family: closeButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined"
font.pixelSize: 20
color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
}
MouseArea {
id: closeButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: settingsModal.closeSettings()
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: Theme.outline
opacity: 0.12
}
}
// Tabs bar (reordered)
Tabs {
id: settingsTabs
Layout.fillWidth: true
tabsModel: [
{ icon: "settings", label: "System" },
{ icon: "wallpaper", label: "Wallpaper" },
{ icon: "cloud", label: "Weather" }
]
}
// Scrollable settings area
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: content.height - settingsTabs.height - header.height - 128
color: "transparent"
border.width: 0
radius: 20
Flickable {
id: flick
anchors.fill: parent
contentWidth: width
contentHeight: tabContentLoader.item ? tabContentLoader.item.implicitHeight : 0
clip: true
Loader {
id: tabContentLoader
anchors.top: parent.top
width: parent.width
sourceComponent: settingsTabs.currentIndex === 0 ? systemTab : settingsTabs.currentIndex === 1 ? wallpaperTab : weatherTab
}
}
Component {
id: systemTab
ColumnLayout {
anchors.fill: parent
ProfileSettings {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
Component {
id: wallpaperTab
ColumnLayout {
anchors.fill: parent
WallpaperSettings {
id: wallpaperSettings
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
Component {
id: weatherTab
ColumnLayout {
anchors.fill: parent
WeatherSettings {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
anchors.margins: 16
}
}
}
}
}
}
// Property to track the settings window instance
property var settingsWindow: null
// Function to open the modal and initialize temp values
function openSettings() {
visible = true;
focusTimer.start();
if (!settingsWindow) {
// Create new window
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
if (settingsWindow) {
settingsWindow.visible = true;
// Handle window closure
settingsWindow.visibleChanged.connect(function() {
if (settingsWindow && !settingsWindow.visible) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
});
}
} else if (settingsWindow.visible) {
// Close and destroy window
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.visible = false;
windowToDestroy.destroy();
}
}
// Function to close the modal and release focus
function closeSettings() {
visible = false;
if (settingsWindow) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.visible = false;
windowToDestroy.destroy();
}
}
Timer {
id: focusTimer
interval: 100
repeat: false
onTriggered: {
if (visible) {
// Focus logic can go here if needed
Component {
id: settingsComponent
SettingsWindow {}
}
// Clean up on destruction
Component.onDestruction: {
if (settingsWindow) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
}

View file

@ -1,722 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Settings
Rectangle {
id: wallpaperSettingsCard
Layout.fillWidth: true
Layout.preferredHeight: Settings.settings.useSWWW ? 720 : 360
color: Theme.surface
radius: 18
ColumnLayout {
anchors.fill: parent
anchors.margins: 18
spacing: 12
RowLayout {
Layout.fillWidth: true
spacing: 12
Text {
text: "image"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Theme.accentPrimary
}
Text {
text: "Wallpaper Settings"
font.family: Theme.fontFamily
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.fillWidth: true
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "Wallpaper Path"
font.family: Theme.fontFamily
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: folderInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: folderInput
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.wallpaperFolder
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhUrlCharactersOnly
onTextChanged: {
Settings.settings.wallpaperFolder = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: folderInput.forceActiveFocus()
}
}
}
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Random Wallpaper"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: randomWallpaperSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.randomWallpaper ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.randomWallpaper ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: randomWallpaperThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.randomWallpaper ? randomWallpaperSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.randomWallpaper = !Settings.settings.randomWallpaper;
}
}
}
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Use Wallpaper Theme"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: wallpaperThemeSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.useWallpaperTheme ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.useWallpaperTheme ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: wallpaperThemeThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.useWallpaperTheme ? wallpaperThemeSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.useWallpaperTheme = !Settings.settings.useWallpaperTheme;
}
}
}
}
ColumnLayout {
spacing: 12
Layout.fillWidth: true
Layout.topMargin: 8
RowLayout {
Layout.fillWidth: true
Text {
text: "Wallpaper Interval (seconds)"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Text {
text: Settings.settings.wallpaperInterval
font.pixelSize: 13
color: Theme.textPrimary
}
}
Slider {
id: intervalSlider
Layout.fillWidth: true
from: 10
to: 900
stepSize: 10
value: Settings.settings.wallpaperInterval
snapMode: Slider.SnapAlways
onMoved: {
Settings.settings.wallpaperInterval = Math.round(value);
}
background: Rectangle {
x: intervalSlider.leftPadding
y: intervalSlider.topPadding + intervalSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: intervalSlider.availableWidth
height: implicitHeight
radius: 2
color: Theme.surfaceVariant
Rectangle {
width: intervalSlider.visualPosition * parent.width
height: parent.height
color: Theme.accentPrimary
radius: 2
}
}
handle: Rectangle {
x: intervalSlider.leftPadding + intervalSlider.visualPosition * (intervalSlider.availableWidth - width)
y: intervalSlider.topPadding + intervalSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: intervalSlider.pressed ? Theme.surfaceVariant : Theme.surface
border.color: Theme.accentPrimary
border.width: 2
}
}
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Use SWWW"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: swwwSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.useSWWW ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.useSWWW ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: swwwThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.useSWWW ? swwwSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.useSWWW = !Settings.settings.useSWWW;
}
}
}
}
ColumnLayout {
spacing: 12
Layout.fillWidth: true
Layout.topMargin: 16
visible: Settings.settings.useSWWW
Text {
text: "Resize Mode"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
ComboBox {
id: resizeComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["no", "crop", "fit", "stretch"]
currentIndex: model.indexOf(Settings.settings.wallpaperResize)
onActivated: {
Settings.settings.wallpaperResize = model[index];
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: resizeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: resizeComboBox.indicator.width + resizeComboBox.spacing
text: resizeComboBox.displayText
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: resizeComboBox.width - width - 12
y: resizeComboBox.topPadding + (resizeComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: resizeComboBox.height
width: resizeComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: resizeComboBox.popup.visible ? resizeComboBox.delegateModel : null
currentIndex: resizeComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {
}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: resizeComboBox.width
highlighted: resizeComboBox.highlightedIndex === index
contentItem: Text {
text: modelData
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
}
}
ColumnLayout {
spacing: 12
Layout.fillWidth: true
Layout.topMargin: 16
visible: Settings.settings.useSWWW
Text {
text: "Transition Type"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
ComboBox {
id: transitionTypeComboBox
Layout.fillWidth: true
Layout.preferredHeight: 40
model: ["none", "simple", "fade", "left", "right", "top", "bottom", "wipe", "wave", "grow", "center", "any", "outer", "random"]
currentIndex: model.indexOf(Settings.settings.transitionType)
onActivated: {
Settings.settings.transitionType = model[index];
}
background: Rectangle {
implicitWidth: 120
implicitHeight: 40
color: Theme.surfaceVariant
border.color: transitionTypeComboBox.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
radius: 16
}
contentItem: Text {
leftPadding: 12
rightPadding: transitionTypeComboBox.indicator.width + transitionTypeComboBox.spacing
text: transitionTypeComboBox.displayText
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Text {
x: transitionTypeComboBox.width - width - 12
y: transitionTypeComboBox.topPadding + (transitionTypeComboBox.availableHeight - height) / 2
text: "arrow_drop_down"
font.family: "Material Symbols Outlined"
font.pixelSize: 24
color: Theme.textPrimary
}
popup: Popup {
y: transitionTypeComboBox.height
width: transitionTypeComboBox.width
implicitHeight: contentItem.implicitHeight
padding: 1
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: transitionTypeComboBox.popup.visible ? transitionTypeComboBox.delegateModel : null
currentIndex: transitionTypeComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {
}
}
background: Rectangle {
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
radius: 16
}
}
delegate: ItemDelegate {
width: transitionTypeComboBox.width
highlighted: transitionTypeComboBox.highlightedIndex === index
contentItem: Text {
text: modelData
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
color: highlighted ? Theme.accentPrimary.toString().replace(/#/, "#1A") : "transparent"
}
}
}
}
ColumnLayout {
spacing: 12
Layout.fillWidth: true
Layout.topMargin: 16
visible: Settings.settings.useSWWW
RowLayout {
Layout.fillWidth: true
Text {
text: "Transition FPS"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Text {
text: Settings.settings.transitionFps
font.pixelSize: 13
color: Theme.textPrimary
}
}
Slider {
id: fpsSlider
Layout.fillWidth: true
from: 30
to: 500
stepSize: 5
value: Settings.settings.transitionFps
snapMode: Slider.SnapAlways
onMoved: {
Settings.settings.transitionFps = Math.round(value);
}
background: Rectangle {
x: fpsSlider.leftPadding
y: fpsSlider.topPadding + fpsSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: fpsSlider.availableWidth
height: implicitHeight
radius: 2
color: Theme.surfaceVariant
Rectangle {
width: fpsSlider.visualPosition * parent.width
height: parent.height
color: Theme.accentPrimary
radius: 2
}
}
handle: Rectangle {
x: fpsSlider.leftPadding + fpsSlider.visualPosition * (fpsSlider.availableWidth - width)
y: fpsSlider.topPadding + fpsSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: fpsSlider.pressed ? Theme.surfaceVariant : Theme.surface
border.color: Theme.accentPrimary
border.width: 2
}
}
}
ColumnLayout {
spacing: 12
Layout.fillWidth: true
Layout.topMargin: 16
visible: Settings.settings.useSWWW
RowLayout {
Layout.fillWidth: true
Text {
text: "Transition Duration (seconds)"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
Text {
text: Settings.settings.transitionDuration.toFixed(3)
font.pixelSize: 13
color: Theme.textPrimary
}
}
Slider {
id: durationSlider
Layout.fillWidth: true
from: 0.25
to: 10
stepSize: 0.05
value: Settings.settings.transitionDuration
snapMode: Slider.SnapAlways
onMoved: {
Settings.settings.transitionDuration = value;
}
background: Rectangle {
x: durationSlider.leftPadding
y: durationSlider.topPadding + durationSlider.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: 4
width: durationSlider.availableWidth
height: implicitHeight
radius: 2
color: Theme.surfaceVariant
Rectangle {
width: durationSlider.visualPosition * parent.width
height: parent.height
color: Theme.accentPrimary
radius: 2
}
}
handle: Rectangle {
x: durationSlider.leftPadding + durationSlider.visualPosition * (durationSlider.availableWidth - width)
y: durationSlider.topPadding + durationSlider.availableHeight / 2 - height / 2
implicitWidth: 20
implicitHeight: 20
radius: 10
color: durationSlider.pressed ? Theme.surfaceVariant : Theme.surface
border.color: Theme.accentPrimary
border.width: 2
}
}
}
}
}

View file

@ -1,275 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Settings
Rectangle {
id: weatherSettingsCard
Layout.fillWidth: true
Layout.preferredHeight: 320
color: Theme.surface
radius: 18
ColumnLayout {
anchors.fill: parent
anchors.margins: 18
spacing: 12
RowLayout {
Layout.fillWidth: true
spacing: 12
Text {
text: "wb_sunny"
font.family: "Material Symbols Outlined"
font.pixelSize: 20
color: Theme.accentPrimary
}
Text {
text: "Weather Settings"
font.family: Theme.fontFamily
font.pixelSize: 16
font.bold: true
color: Theme.textPrimary
Layout.fillWidth: true
}
}
ColumnLayout {
spacing: 8
Layout.fillWidth: true
Text {
text: "City"
font.family: Theme.fontFamily
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: 16
color: Theme.surfaceVariant
border.color: cityInput.activeFocus ? Theme.accentPrimary : Theme.outline
border.width: 1
TextInput {
id: cityInput
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.leftMargin: 12
anchors.rightMargin: 12
anchors.topMargin: 6
anchors.bottomMargin: 6
text: Settings.settings.weatherCity
font.family: Theme.fontFamily
font.pixelSize: 13
color: Theme.textPrimary
verticalAlignment: TextInput.AlignVCenter
clip: true
focus: true
selectByMouse: true
activeFocusOnTab: true
inputMethodHints: Qt.ImhNone
onTextChanged: {
Settings.settings.weatherCity = text;
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.IBeamCursor
onClicked: {
cityInput.forceActiveFocus();
}
}
}
}
}
RowLayout {
spacing: 12
Layout.fillWidth: true
Text {
text: "Temperature Unit"
font.family: Theme.fontFamily
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: customSwitch
width: 52
height: 32
radius: 16
color: Theme.accentPrimary
border.color: Theme.accentPrimary
border.width: 2
Rectangle {
id: thumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.useFahrenheit ? customSwitch.width - width - 2 : 2
Text {
anchors.centerIn: parent
text: Settings.settings.useFahrenheit ? "\u00b0F" : "\u00b0C"
font.family: Theme.fontFamily
font.pixelSize: 12
font.bold: true
color: Theme.textPrimary
}
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.useFahrenheit = !Settings.settings.useFahrenheit;
}
}
}
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "Use 12 Hour Clock"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: use12HourClockSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.use12HourClock ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: randomWallpaperThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.use12HourClock ? use12HourClockSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.use12HourClock = !Settings.settings.use12HourClock;
}
}
}
}
RowLayout {
spacing: 8
Layout.fillWidth: true
Layout.topMargin: 8
Text {
text: "US Style Date"
font.pixelSize: 13
font.bold: true
color: Theme.textPrimary
}
Item {
Layout.fillWidth: true
}
// Custom Material 3 Switch
Rectangle {
id: reverseDayMonthSwitch
width: 52
height: 32
radius: 16
color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.surfaceVariant
border.color: Settings.settings.reverseDayMonth ? Theme.accentPrimary : Theme.outline
border.width: 2
Rectangle {
id: reverseDayMonthThumb
width: 28
height: 28
radius: 14
color: Theme.surface
border.color: Theme.outline
border.width: 1
y: 2
x: Settings.settings.reverseDayMonth ? reverseDayMonthSwitch.width - width - 2 : 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.settings.reverseDayMonth = !Settings.settings.reverseDayMonth;
}
}
}
}
}
}

View file

@ -4,11 +4,22 @@ import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Settings
import qs.Widgets.Sidebar.Config
import qs.Widgets.SettingsWindow
import qs.Components
PanelWithOverlay {
id: sidebarPopup
property var shell: null
// Trigger initial weather loading when component is completed
Component.onCompleted: {
// Load initial weather data after a short delay to ensure all components are ready
Qt.callLater(function() {
if (weather && weather.fetchCityWeather) {
weather.fetchCityWeather();
}
});
}
function showAt() {
sidebarPopupRect.showAt();
@ -56,17 +67,17 @@ PanelWithOverlay {
}
function hidePopup() {
if (sidebarPopupRect.settingsModal && sidebarPopupRect.settingsModal.visible) {
sidebarPopupRect.settingsModal.visible = false;
if (shell && shell.settingsWindow && shell.settingsWindow.visible) {
shell.settingsWindow.visible = false;
}
if (wallpaperPanel && wallpaperPanel.visible) {
wallpaperPanel.visible = false;
if (wallpaperPanelLoader.active && wallpaperPanelLoader.item && wallpaperPanelLoader.item.visible) {
wallpaperPanelLoader.item.visible = false;
}
if (sidebarPopupRect.wifiPanelModal && sidebarPopupRect.wifiPanelModal.visible) {
sidebarPopupRect.wifiPanelModal.visible = false;
if (wifiPanelLoader.active && wifiPanelLoader.item && wifiPanelLoader.item.visible) {
wifiPanelLoader.item.visible = false;
}
if (sidebarPopupRect.bluetoothPanelModal && sidebarPopupRect.bluetoothPanelModal.visible) {
sidebarPopupRect.bluetoothPanelModal.visible = false;
if (bluetoothPanelLoader.active && bluetoothPanelLoader.item && bluetoothPanelLoader.item.visible) {
bluetoothPanelLoader.item.visible = false;
}
if (sidebarPopup.visible) {
slideAnim.from = 0;
@ -124,11 +135,44 @@ PanelWithOverlay {
}
}
property alias settingsModal: settingsModal
property alias wifiPanelModal: wifiPanel.panel
property alias bluetoothPanelModal: bluetoothPanel.panel
SettingsModal {
// Access the shell's SettingsWindow instead of creating a new one
// LazyLoader for WifiPanel
LazyLoader {
id: wifiPanelLoader
loading: false
component: WifiPanel {}
}
// LazyLoader for BluetoothPanel
LazyLoader {
id: bluetoothPanelLoader
loading: false
component: BluetoothPanel {}
}
// LazyLoader for WallpaperPanel
LazyLoader {
id: wallpaperPanelLoader
loading: false
component: WallpaperPanel {
Component.onCompleted: {
if (parent) {
anchors.top = parent.top;
anchors.right = parent.right;
}
}
}
}
// SettingsIcon component
SettingsIcon {
id: settingsModal
onWeatherRefreshRequested: {
if (weather && weather.fetchCityWeather) {
weather.fetchCityWeather();
}
}
}
Item {
@ -226,7 +270,14 @@ PanelWithOverlay {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: wifiPanel.showAt()
onClicked: {
if (!wifiPanelLoader.active) {
wifiPanelLoader.loading = true;
}
if (wifiPanelLoader.item) {
wifiPanelLoader.item.showAt();
}
}
}
StyledTooltip {
@ -261,7 +312,14 @@ PanelWithOverlay {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: bluetoothPanel.showAt()
onClicked: {
if (!bluetoothPanelLoader.active) {
bluetoothPanelLoader.loading = true;
}
if (bluetoothPanelLoader.item) {
bluetoothPanelLoader.item.showAt();
}
}
}
StyledTooltip {
@ -274,16 +332,6 @@ PanelWithOverlay {
}
}
// Hidden panel components for modal functionality
WifiPanel {
id: wifiPanel
visible: false
}
BluetoothPanel {
id: bluetoothPanel
visible: false
}
Item {
Layout.fillHeight: true
}
@ -310,10 +358,18 @@ PanelWithOverlay {
}
onSettingsRequested: {
settingsModal.visible = true;
// Use the SettingsModal's openSettings function
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
settingsModal.openSettings();
}
}
onWallpaperRequested: {
wallpaperPanel.visible = true;
if (!wallpaperPanelLoader.active) {
wallpaperPanelLoader.loading = true;
}
if (wallpaperPanelLoader.item) {
wallpaperPanelLoader.item.visible = true;
}
}
}
}
@ -408,15 +464,5 @@ PanelWithOverlay {
}
}
}
WallpaperPanel {
id: wallpaperPanel
Component.onCompleted: {
if (parent) {
anchors.top = parent.top;
anchors.right = parent.right;
}
}
}
}
}

View file

@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import qs.Settings
import qs.Services
import qs.Widgets.SettingsWindow
import qs.Components
PanelWindow {
id: settingsModal
implicitWidth: 480
implicitHeight: 780
visible: false
color: "transparent"
anchors.top: true
anchors.right: true
margins.right: 0
margins.top: 0
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
// Signal to request weather refresh
signal weatherRefreshRequested()
// Property to track the settings window instance
property var settingsWindow: null
// Function to open the modal and initialize temp values
function openSettings() {
if (!settingsWindow) {
// Create new window
settingsWindow = settingsComponent.createObject(null); // No parent to avoid dependency issues
if (settingsWindow) {
settingsWindow.visible = true;
// Handle window closure
settingsWindow.visibleChanged.connect(function() {
if (settingsWindow && !settingsWindow.visible) {
// Trigger weather refresh when settings close
weatherRefreshRequested();
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
});
}
} else if (settingsWindow.visible) {
// Close and destroy window
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.visible = false;
windowToDestroy.destroy();
}
}
// Function to close the modal and release focus
function closeSettings() {
if (settingsWindow) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.visible = false;
windowToDestroy.destroy();
}
}
Component {
id: settingsComponent
SettingsWindow {}
}
// Clean up on destruction
Component.onDestruction: {
if (settingsWindow) {
var windowToDestroy = settingsWindow;
settingsWindow = null;
windowToDestroy.destroy();
}
}
}

View file

@ -15,13 +15,17 @@ Rectangle {
property var weatherData: null
property string errorString: ""
property bool isVisible: false
property int lastFetchTime: 0
property bool isLoading: false
// Auto-refetch weather when city changes
Connections {
target: Settings.settings
function onWeatherCityChanged() {
if (isVisible && city !== "") {
fetchCityWeather()
// Force refresh when city changes
lastFetchTime = 0;
fetchCityWeather();
}
}
}
@ -33,20 +37,42 @@ Rectangle {
}
function fetchCityWeather() {
if (!city || city.trim() === "") {
errorString = "No city configured";
return;
}
// Check if we should fetch new data (avoid fetching too frequently)
var currentTime = Date.now();
var timeSinceLastFetch = currentTime - lastFetchTime;
// Only skip if we have recent data AND lastFetchTime is not 0 (initial state)
if (lastFetchTime > 0 && timeSinceLastFetch < 60000) { // 1 minute
return; // Skip if last fetch was less than 1 minute ago
}
isLoading = true;
errorString = "";
WeatherHelper.fetchCityWeather(city,
function(result) {
weatherData = result.weather;
lastFetchTime = currentTime;
errorString = "";
isLoading = false;
},
function(err) {
errorString = err;
isLoading = false;
}
);
}
function startWeatherFetch() {
isVisible = true
fetchCityWeather()
// Force refresh when panel opens, regardless of time check
lastFetchTime = 0;
fetchCityWeather();
}
function stopWeatherFetch() {
@ -77,12 +103,21 @@ Rectangle {
Text {
id: weatherIcon
text: weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud"
text: isLoading ? "sync" : (weatherData && weatherData.current_weather ? materialSymbolForCode(weatherData.current_weather.weathercode) : "cloud")
font.family: "Material Symbols Outlined"
font.pixelSize: 28
verticalAlignment: Text.AlignVCenter
color: Theme.accentPrimary
color: isLoading ? Theme.accentPrimary : Theme.accentPrimary
Layout.alignment: Qt.AlignVCenter
// Add rotation animation for loading state
RotationAnimation on rotation {
running: isLoading
from: 0
to: 360
duration: 1000
loops: Animation.Infinite
}
}
ColumnLayout {

View file

@ -9,6 +9,7 @@ import qs.Bar.Modules
import qs.Widgets
import qs.Widgets.LockScreen
import qs.Widgets.Notification
import qs.Widgets.SettingsWindow
import qs.Settings
import qs.Helpers
@ -19,9 +20,18 @@ Scope {
id: root
property alias appLauncherPanel: appLauncherPanel
property var notificationHistoryWin: notificationHistoryWin
property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null
property var settingsWindow: null
property bool pendingReload: false
// Function to load notification history
function loadNotificationHistory() {
if (!notificationHistoryLoader.active) {
notificationHistoryLoader.loading = true;
}
return notificationHistoryLoader;
}
// Helper function to round value to nearest step
function roundToStep(value, step) {
return Math.round(value / step) * step;
@ -50,7 +60,7 @@ Scope {
Bar {
id: bar
shell: root
property var notificationHistoryWin: notificationHistoryWin
property var notificationHistoryWin: notificationHistoryLoader.active ? notificationHistoryLoader.item : null
}
Variants {
@ -62,6 +72,9 @@ Scope {
}
}
Background {}
Overview {}
Applauncher {
id: appLauncherPanel
visible: false
@ -94,8 +107,8 @@ Scope {
}
}
}
if (notificationHistoryWin) {
notificationHistoryWin.addToHistory({
if (notificationHistoryLoader.active && notificationHistoryLoader.item) {
notificationHistoryLoader.item.addToHistory({
id: notification.id,
appName: notification.appName || "Notification",
summary: notification.summary || "",
@ -111,8 +124,33 @@ Scope {
id: notificationPopup
}
NotificationHistory {
id: notificationHistoryWin
// LazyLoader for NotificationHistory - only load when needed
LazyLoader {
id: notificationHistoryLoader
loading: false
component: NotificationHistory {}
}
// Centralized LazyLoader for SettingsWindow - prevents crashes on multiple opens
LazyLoader {
id: settingsWindowLoader
loading: false
component: SettingsWindow {
Component.onCompleted: {
root.settingsWindow = this;
}
}
}
// Function to safely show/hide settings window
function toggleSettingsWindow() {
if (!settingsWindowLoader.active) {
settingsWindowLoader.loading = true;
}
if (settingsWindowLoader.item) {
settingsWindowLoader.item.visible = !settingsWindowLoader.item.visible;
}
}
// Reference to the default audio sink from Pipewire