mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge branch 'noctalia-dev:main' into main
This commit is contained in:
@@ -1511,10 +1511,6 @@
|
||||
"description": "Die Paneele rasten an der Stange und den Bildschirmrändern ein und sorgen so für ein nahtloses Erscheinungsbild mit stilvollen, umgekehrten Ecken.",
|
||||
"label": "Paneele an Kanten ausrichten"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Richten Sie das Einstellungsfenster an der Leiste aus, um ein einheitliches Erscheinungsbild zu erhalten.",
|
||||
"label": "Einstellungsfenster an Leiste ausrichten"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Stellt sicher, dass Panels und die Leiste sichtbar bleiben, auch über Vollbildanwendungen.",
|
||||
"label": "Panels & Leiste oben behalten"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Passen Sie das Aussehen, die Haptik und das Verhalten der Benutzeroberfläche an.",
|
||||
"label": "Aussehen"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Richten Sie das Einstellungsfenster an der Leiste aus, um ein einheitliches Erscheinungsbild zu erhalten.",
|
||||
"label": "Einstellungsfenster an Leiste ausrichten"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Aktiviert Schlagschatten unter Balken und Panels.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Normaler Modus",
|
||||
"not-installed": "wlsunset nicht installiert"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Leistungsmodus deaktiviert",
|
||||
"enabled": "Leistungsmodus aktiviert",
|
||||
"label": "Noctalia-Aufführung"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Energieprofil geändert",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Panels lock to the bar and screen edges, creating a seamless look with stylish inverted corners.",
|
||||
"label": "Snap panels to edges"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Keep the Settings window aligned with the bar for a unified look.",
|
||||
"label": "Snap Settings window to bar"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Ensures panels and the bar remain visible, even over fullscreen applications.",
|
||||
"label": "Keep panels & bar on top"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Customize the look, feel, and behavior of the interface.",
|
||||
"label": "Appearance"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Keep the Settings window aligned with the bar for a unified look.",
|
||||
"label": "Snap Settings window to bar"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Enables drop shadows under bars and panels.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Normal mode",
|
||||
"not-installed": "wlsunset not installed"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Performance mode disabled",
|
||||
"enabled": "Performance mode enabled",
|
||||
"label": "Noctalia performance"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Power profile changed",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Los paneles se fijan a la barra y a los bordes de la pantalla, creando una apariencia impecable con elegantes esquinas invertidas.",
|
||||
"label": "Ajustar paneles a los bordes"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Mantén la ventana de Configuración alineada con la barra para un aspecto unificado.",
|
||||
"label": "Ajustar Configuración a la barra"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Garantiza que los paneles y la barra permanezcan visibles, incluso sobre aplicaciones en pantalla completa.",
|
||||
"label": "Mantener paneles y barra en la parte superior"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Personaliza la apariencia, el ambiente y el comportamiento de la interfaz.",
|
||||
"label": "Apariencia"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Mantén la ventana de Configuración alineada con la barra para un aspecto unificado.",
|
||||
"label": "Ajustar Configuración a la barra"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Habilita sombras paralelas debajo de las barras y los paneles.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Modo normal",
|
||||
"not-installed": "wlsunset no está instalado"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Modo de rendimiento desactivado",
|
||||
"enabled": "Modo de rendimiento activado",
|
||||
"label": "Actuación de Noctalia"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Perfil de energía cambiado",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Les panneaux se verrouillent sur la barre et les bords de l'écran, créant un aspect homogène avec d'élégants coins inversés.",
|
||||
"label": "Aligner les panneaux aux bords"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Alignez la fenêtre des paramètres sur la barre pour un rendu cohérent.",
|
||||
"label": "Aligner la fenêtre des paramètres sur la barre"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Garantit que les panneaux et la barre restent visibles, même au-dessus des applications en plein écran.",
|
||||
"label": "Garder les panneaux et la barre en haut."
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Personnaliser l'apparence, l'ergonomie et le comportement de l'interface.",
|
||||
"label": "Apparence"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Alignez la fenêtre des paramètres sur la barre pour un rendu cohérent.",
|
||||
"label": "Aligner la fenêtre des paramètres sur la barre"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Active les ombres portées sous les barres et les panneaux.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Mode normal",
|
||||
"not-installed": "wlsunset non installé"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Mode performance désactivé",
|
||||
"enabled": "Mode performance activé",
|
||||
"label": "Performance de Noctalia"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Profil d'alimentation changé",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Os painéis travam na barra e nas bordas da tela, criando uma aparência perfeita com cantos invertidos elegantes.",
|
||||
"label": "Ajustar Painéis às Bordas"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Mantenha a janela de Configurações alinhada com a barra para um visual uniforme.",
|
||||
"label": "Ajustar Configurações à barra"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Garante que os painéis e a barra permaneçam visíveis, mesmo sobre aplicativos em tela cheia.",
|
||||
"label": "Manter Painéis e Barra no Topo"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Personalize a aparência, a sensação e o comportamento da interface.",
|
||||
"label": "Aparência"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Mantenha a janela de Configurações alinhada com a barra para um visual uniforme.",
|
||||
"label": "Ajustar Configurações à barra"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Ativa sombras projetadas sob barras e painéis.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Modo normal",
|
||||
"not-installed": "wlsunset não está instalado"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Modo de desempenho desativado",
|
||||
"enabled": "Modo de desempenho ativado",
|
||||
"label": "Apresentação de Noctalia"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Perfil de energia alterado",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Paneller çubuğa ve ekran kenarlarına kilitlenerek şık ters köşelerle kusursuz bir görünüm oluşturur.",
|
||||
"label": "Kenarları Yakala"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Ayarlar penceresini çubukla hizalayarak tutarlı bir görünüm sağlayın.",
|
||||
"label": "Ayarlar penceresini çubuğa hizala"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Panellerin ve çubuğun, tam ekran uygulamalarda bile görünür kalmasını sağlar.",
|
||||
"label": "Panelleri ve Çubuğu Üstte Tut"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Arayüzün görünümünü, hissini ve davranışını özelleştirin.",
|
||||
"label": "Görünüm"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Ayarlar penceresini çubukla hizalayarak tutarlı bir görünüm sağlayın.",
|
||||
"label": "Ayarlar penceresini çubuğa hizala"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Çubukların ve panellerin altında gölgelerin etkinleştirilmesini sağlar.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Normal mod",
|
||||
"not-installed": "wlsunset kurulu değil"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Performans modu devre dışı bırakıldı",
|
||||
"enabled": "Performans modu etkinleştirildi",
|
||||
"label": "Noctalia performansı"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Güç profili değişti",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "Панелі фіксуються до штанги та країв екрана, створюючи бездоганний вигляд зі стильними перевернутими кутами.",
|
||||
"label": "Прив'язувати панелі до країв"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Вирівнюйте вікно налаштувань відносно панелі, щоб зберегти цілісний вигляд.",
|
||||
"label": "Прив'язувати вікно налаштувань до панелі"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "Забезпечує видимість панелей і панелі завдань, навіть поверх повноекранних програм.",
|
||||
"label": "Тримати панелі та панель зверху"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "Налаштуйте вигляд, відчуття та поведінку інтерфейсу.",
|
||||
"label": "Зовнішній вигляд"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "Вирівнюйте вікно налаштувань відносно панелі, щоб зберегти цілісний вигляд.",
|
||||
"label": "Прив'язувати вікно налаштувань до панелі"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "Увімкнути тіні під панелями та смугами.",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "Звичайний режим",
|
||||
"not-installed": "wlsunset не встановлено"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "Режим продуктивності вимкнено.",
|
||||
"enabled": "Режим продуктивності ввімкнено.",
|
||||
"label": "Виступ Noctalia"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "Профіль живлення змінено",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
@@ -1511,10 +1511,6 @@
|
||||
"description": "面板锁定到边栏和屏幕边缘,形成无缝外观,并具有时尚的倒角。",
|
||||
"label": "将面板对齐到边缘"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "使设置窗口与边栏保持对齐,实现统一的外观。",
|
||||
"label": "将设置窗口贴合边栏"
|
||||
},
|
||||
"panels-overlay": {
|
||||
"description": "确保面板和栏保持可见,即使在全屏应用程序上也是如此。",
|
||||
"label": "将面板和栏保持在顶部"
|
||||
@@ -1528,6 +1524,10 @@
|
||||
"description": "自定义界面的外观、感觉和行为。",
|
||||
"label": "外观"
|
||||
},
|
||||
"settings-panel-attached-to-bar": {
|
||||
"description": "使设置窗口与边栏保持对齐,实现统一的外观。",
|
||||
"label": "将设置窗口贴合边栏"
|
||||
},
|
||||
"shadows": {
|
||||
"description": "启用条形图和面板下的阴影。",
|
||||
"direction": {
|
||||
@@ -1734,6 +1734,11 @@
|
||||
"normal": "正常模式",
|
||||
"not-installed": "未安装 wlsunset"
|
||||
},
|
||||
"noctalia-performance": {
|
||||
"disabled": "性能模式已禁用",
|
||||
"enabled": "已启用性能模式",
|
||||
"label": "夜之颂演出"
|
||||
},
|
||||
"power-profile": {
|
||||
"changed": "电源模式已更改",
|
||||
"profile-name": "\"{profile}\""
|
||||
|
||||
+8
-6
@@ -4,13 +4,15 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/*
|
||||
Preset sizes for font, radii, ?
|
||||
*/
|
||||
*/
|
||||
|
||||
// Font size
|
||||
property real fontSizeXXS: 8
|
||||
@@ -65,11 +67,11 @@ Singleton {
|
||||
property real shadowVerticalOffset: Settings.data.general.shadowOffsetY
|
||||
|
||||
// Animation duration (ms)
|
||||
property int animationFaster: Settings.data.general.animationDisabled ? 0 : Math.round(75 / Settings.data.general.animationSpeed)
|
||||
property int animationFast: Settings.data.general.animationDisabled ? 0 : Math.round(150 / Settings.data.general.animationSpeed)
|
||||
property int animationNormal: Settings.data.general.animationDisabled ? 0 : Math.round(300 / Settings.data.general.animationSpeed)
|
||||
property int animationSlow: Settings.data.general.animationDisabled ? 0 : Math.round(450 / Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: Settings.data.general.animationDisabled ? 0 : Math.round(750 / Settings.data.general.animationSpeed)
|
||||
property int animationFaster: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(75 / Settings.data.general.animationSpeed)
|
||||
property int animationFast: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(150 / Settings.data.general.animationSpeed)
|
||||
property int animationNormal: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(300 / Settings.data.general.animationSpeed)
|
||||
property int animationSlow: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(450 / Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(750 / Settings.data.general.animationSpeed)
|
||||
|
||||
// Delays
|
||||
property int tooltipDelay: 300
|
||||
|
||||
@@ -17,6 +17,7 @@ Loader {
|
||||
|
||||
required property ShellScreen modelData
|
||||
property string wallpaper: ""
|
||||
property bool overviewIsSeen: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData) {
|
||||
@@ -25,6 +26,13 @@ Loader {
|
||||
setWallpaperInitial()
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Clean up resources to prevent memory leak when overviewEnabled is toggled off
|
||||
timerDisableFx.stop()
|
||||
bgImage.layer.enabled = false
|
||||
bgImage.source = ""
|
||||
}
|
||||
|
||||
// External state management
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
@@ -35,6 +43,27 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService.backend
|
||||
function onOverviewActiveChanged() {
|
||||
if (CompositorService.backend.overviewActive) {
|
||||
overviewIsSeen = true
|
||||
timerDisableFx.stop()
|
||||
} else {
|
||||
timerDisableFx.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use to disable effects when overview is no longer necessary.
|
||||
Timer {
|
||||
id: timerDisableFx
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
overviewIsSeen = false
|
||||
}
|
||||
}
|
||||
|
||||
function setWallpaperInitial() {
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
Qt.callLater(setWallpaperInitial)
|
||||
@@ -64,23 +93,28 @@ Loader {
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
source: wallpaper
|
||||
smooth: true
|
||||
smooth: false
|
||||
mipmap: false
|
||||
cache: false
|
||||
asynchronous: true
|
||||
// Image is heavily blurred, so might as well save a lot of memory here.
|
||||
sourceSize: Qt.size(1280, 720)
|
||||
}
|
||||
visible: overviewIsSeen
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: bgImage
|
||||
autoPaddingEnabled: false
|
||||
blurEnabled: true
|
||||
blur: 1.0
|
||||
blurMax: 64
|
||||
colorization: Style.opacityMedium
|
||||
colorizationColor: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface
|
||||
// Image is heavily blurred, so use low resolution to save memory
|
||||
sourceSize: Qt.size(1280, 720)
|
||||
|
||||
layer.enabled: overviewIsSeen
|
||||
layer.smooth: false
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
blur: 1.0
|
||||
blurMax: 32
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Settings.data.colorSchemes.darkMode ? Color.mSurface : Color.mOnSurface
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ Item {
|
||||
property bool effectsActive: false
|
||||
property color effectColor: Color.mPrimary
|
||||
|
||||
property int horizontalPadding: Math.round(Style.marginS)
|
||||
property int spacingBetweenPills: Math.round(Style.marginXS)
|
||||
property int horizontalPadding: Style.marginS
|
||||
property int spacingBetweenPills: Style.marginXS
|
||||
|
||||
// Wheel scroll handling
|
||||
property int wheelAccumulatedDelta: 0
|
||||
|
||||
@@ -78,7 +78,9 @@ PanelWindow {
|
||||
|
||||
// Desktop dimming when panels are open
|
||||
property bool dimDesktop: Settings.data.general.dimDesktop
|
||||
property bool isPanelOpen: PanelService.openedPanel !== null
|
||||
property bool isPanelOpen: (PanelService.openedPanel !== null) && (PanelService.openedPanel.screen === screen)
|
||||
property bool isPanelClosing: (PanelService.openedPanel !== null) && PanelService.openedPanel.isClosing
|
||||
|
||||
color: {
|
||||
if (dimDesktop && isPanelOpen) {
|
||||
return Qt.alpha(Color.mShadow, 0.8)
|
||||
@@ -124,30 +126,8 @@ PanelWindow {
|
||||
id: backgroundMaskRegion
|
||||
x: 0
|
||||
y: 0
|
||||
width: root.isPanelOpen ? root.width : 0
|
||||
height: root.isPanelOpen ? root.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
// Variants for panel regions
|
||||
Variants {
|
||||
id: panelRegions
|
||||
|
||||
// Model is the list of open panels (filters out closed ones)
|
||||
model: [audioPanel, controlCenterPanel, calendarPanel].filter(function (p) {
|
||||
return p && p.isPanelOpen
|
||||
})
|
||||
|
||||
Region {
|
||||
required property var modelData
|
||||
property var panelBg: modelData.panelRegion
|
||||
|
||||
// Direct bindings
|
||||
x: panelBg?.x ?? 0
|
||||
y: panelBg?.y ?? 0
|
||||
width: panelBg?.width ?? 0
|
||||
height: panelBg?.height ?? 0
|
||||
width: root.isPanelOpen && !isPanelClosing ? root.width : 0
|
||||
height: root.isPanelOpen && !isPanelClosing ? root.height : 0
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
@@ -174,11 +154,12 @@ PanelWindow {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.isPanelOpen
|
||||
onClicked: {
|
||||
if (PanelService.openedPanel) {
|
||||
PanelService.openedPanel.close()
|
||||
}
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (PanelService.openedPanel) {
|
||||
PanelService.openedPanel.close()
|
||||
}
|
||||
}
|
||||
z: 0 // Behind panels and bar
|
||||
}
|
||||
|
||||
|
||||
@@ -58,23 +58,38 @@ Variants {
|
||||
readonly property bool isRight: location.endsWith("_right")
|
||||
readonly property bool isCentered: location === "top" || location === "bottom"
|
||||
|
||||
readonly property string barPos: Settings.data.bar.position
|
||||
readonly property bool isFloating: Settings.data.bar.floating
|
||||
|
||||
readonly property int notifWidth: Math.round(420 * Style.uiScaleRatio)
|
||||
|
||||
// Calculate bar offset once
|
||||
readonly property int barOffset: {
|
||||
const barPos = Settings.data.bar.position
|
||||
const isFloating = Settings.data.bar.floating
|
||||
// Calculate bar offsets for each edge separately
|
||||
readonly property int barOffsetTop: {
|
||||
if (barPos !== "top")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
}
|
||||
|
||||
// Check if bar is on same edge as notifications
|
||||
if ((isTop && barPos === "top") || (isBottom && barPos === "bottom")) {
|
||||
return Style.barHeight + floatMarginV
|
||||
}
|
||||
if ((isLeft && barPos === "left") || (isRight && barPos === "right")) {
|
||||
return Style.barHeight + floatMarginH
|
||||
}
|
||||
return 0
|
||||
readonly property int barOffsetBottom: {
|
||||
if (barPos !== "bottom")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
}
|
||||
|
||||
readonly property int barOffsetLeft: {
|
||||
if (barPos !== "left")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
}
|
||||
|
||||
readonly property int barOffsetRight: {
|
||||
if (barPos !== "right")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
}
|
||||
|
||||
// Anchoring
|
||||
@@ -83,11 +98,11 @@ Variants {
|
||||
anchors.left: isLeft
|
||||
anchors.right: isRight
|
||||
|
||||
// Margins for PanelWindow (not anchors.topMargin!)
|
||||
margins.top: isTop ? Style.marginM + barOffset : 0
|
||||
margins.bottom: isBottom ? Style.marginM + barOffset : 0
|
||||
margins.left: isLeft ? Style.marginM + barOffset : 0
|
||||
margins.right: isRight ? Style.marginM + barOffset : 0
|
||||
// Margins for PanelWindow - only apply bar offset for the specific edge where the bar is
|
||||
margins.top: isTop ? Style.marginM + barOffsetTop : 0
|
||||
margins.bottom: isBottom ? Style.marginM + barOffsetBottom : 0
|
||||
margins.left: isLeft ? Style.marginM + barOffsetLeft : 0
|
||||
margins.right: isRight ? Style.marginM + barOffsetRight : 0
|
||||
|
||||
implicitWidth: notifWidth
|
||||
implicitHeight: notificationStack.implicitHeight + Style.marginL
|
||||
@@ -186,7 +201,6 @@ Variants {
|
||||
id: cardBackground
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
anchors.rightMargin: Style.marginXL
|
||||
radius: Style.radiusL
|
||||
border.color: Qt.alpha(Color.mOutline, Settings.data.notifications.backgroundOpacity || 1.0)
|
||||
border.width: Style.borderS
|
||||
@@ -373,7 +387,6 @@ Variants {
|
||||
id: notificationContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
anchors.rightMargin: Style.marginXL
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
@@ -433,10 +446,11 @@ Variants {
|
||||
color: Color.mOnSurface
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 3
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Style.marginM
|
||||
}
|
||||
|
||||
NText {
|
||||
@@ -445,10 +459,12 @@ Variants {
|
||||
color: Color.mOnSurface
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.fillWidth: true
|
||||
|
||||
maximumLineCount: 5
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Style.marginXL
|
||||
}
|
||||
|
||||
// Actions
|
||||
@@ -508,7 +524,7 @@ Variants {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.marginXL
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.marginXL * 1.5
|
||||
anchors.rightMargin: Style.marginXL
|
||||
|
||||
onClicked: {
|
||||
NotificationService.removeFromHistory(model.id)
|
||||
|
||||
@@ -11,10 +11,14 @@ import qs.Widgets
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
// Track whether we have an active media player
|
||||
readonly property bool hasActivePlayer: MediaService.currentPlayer && MediaService.canPlay
|
||||
|
||||
// Wrapper - rounded rect clipper
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
layer.effect: MultiEffect {
|
||||
maskEnabled: true
|
||||
maskThresholdMin: 0.5
|
||||
@@ -34,16 +38,9 @@ NBox {
|
||||
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
|
||||
id: bgImage
|
||||
anchors.fill: parent
|
||||
source: MediaService.trackArtUrl || WallpaperService.getWallpaper(Screen.name)
|
||||
source: MediaService.trackArtUrl || WallpaperService.getWallpaper(screen.name)
|
||||
sourceSize: Qt.size(dim, dim)
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
blur: 0.25
|
||||
blurMax: 16
|
||||
}
|
||||
}
|
||||
|
||||
// Dark overlay for readability
|
||||
@@ -189,245 +186,205 @@ NBox {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
|
||||
// No media player detected
|
||||
ColumnLayout {
|
||||
id: fallback
|
||||
// No media player detected - use Loader for performance
|
||||
Loader {
|
||||
id: fallbackLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
active: !root.hasActivePlayer
|
||||
|
||||
visible: !main.visible
|
||||
spacing: Style.marginS
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginL
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginL
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: Style.fontSizeXXXL * 4
|
||||
Layout.preferredHeight: Style.fontSizeXXXL * 4
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: Style.fontSizeXXXL * 4
|
||||
Layout.preferredHeight: Style.fontSizeXXXL * 4
|
||||
|
||||
// Pulsating audio circles (background)
|
||||
Repeater {
|
||||
model: 3
|
||||
Rectangle {
|
||||
// Center icon (static, no animation to avoid GPU cost)
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width * (1.0 + index * 0.2)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: Color.mOnSurfaceVariant
|
||||
border.width: 2
|
||||
opacity: 0
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
PauseAnimation {
|
||||
duration: index * 600
|
||||
}
|
||||
NumberAnimation {
|
||||
from: 1.0
|
||||
to: 0
|
||||
duration: 2000
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation on scale {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
PauseAnimation {
|
||||
duration: index * 600
|
||||
}
|
||||
NumberAnimation {
|
||||
from: 0.6
|
||||
to: 1.2
|
||||
duration: 2000
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
icon: "disc"
|
||||
pointSize: Style.fontSizeXXXL * 3
|
||||
color: Color.mOnSurfaceVariant
|
||||
opacity: 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Spinning disc
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "disc"
|
||||
pointSize: Style.fontSizeXXXL * 3
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
// Descriptive text
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Style.marginXS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MediaPlayer Main Content
|
||||
ColumnLayout {
|
||||
id: main
|
||||
// MediaPlayer Main Content - use Loader for performance
|
||||
Loader {
|
||||
id: mainLoader
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
active: root.hasActivePlayer
|
||||
|
||||
visible: MediaService.currentPlayer && MediaService.canPlay
|
||||
spacing: Style.marginS
|
||||
sourceComponent: ColumnLayout {
|
||||
id: main
|
||||
spacing: Style.marginS
|
||||
|
||||
// Spacer to push content down
|
||||
Item {
|
||||
Layout.preferredHeight: Style.marginM
|
||||
}
|
||||
// Spacer to push content down
|
||||
Item {
|
||||
Layout.preferredHeight: Style.marginM
|
||||
}
|
||||
|
||||
// Metadata at the bottom left
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
spacing: Style.marginXS
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackTitle !== ""
|
||||
text: MediaService.trackTitle
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
// Metadata at the bottom left
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
spacing: Style.marginXS
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackTitle !== ""
|
||||
text: MediaService.trackTitle
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackArtist !== ""
|
||||
text: MediaService.trackArtist
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackAlbum !== ""
|
||||
text: MediaService.trackAlbum
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackArtist !== ""
|
||||
text: MediaService.trackArtist
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
elide: Text.ElideRight
|
||||
// Progress slider
|
||||
Item {
|
||||
id: progressWrapper
|
||||
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
height: Style.baseWidgetSize * 0.5
|
||||
|
||||
NText {
|
||||
visible: MediaService.trackAlbum !== ""
|
||||
text: MediaService.trackAlbum
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeM
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
property real localSeekRatio: -1
|
||||
property real lastSentSeekRatio: -1
|
||||
property real seekEpsilon: 0.01
|
||||
property real progressRatio: {
|
||||
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
|
||||
return 0
|
||||
const r = MediaService.currentPosition / MediaService.trackLength
|
||||
if (isNaN(r) || !isFinite(r))
|
||||
return 0
|
||||
return Math.max(0, Math.min(1, r))
|
||||
}
|
||||
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||
|
||||
// Progress slider
|
||||
Item {
|
||||
id: progressWrapper
|
||||
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
|
||||
Layout.fillWidth: true
|
||||
height: Style.baseWidgetSize * 0.5
|
||||
|
||||
property real localSeekRatio: -1
|
||||
property real lastSentSeekRatio: -1
|
||||
property real seekEpsilon: 0.01
|
||||
property real progressRatio: {
|
||||
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
|
||||
return 0
|
||||
const r = MediaService.currentPosition / MediaService.trackLength
|
||||
if (isNaN(r) || !isFinite(r))
|
||||
return 0
|
||||
return Math.max(0, Math.min(1, r))
|
||||
}
|
||||
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||
|
||||
Timer {
|
||||
id: seekDebounce
|
||||
interval: 75
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
MediaService.seekByRatio(next)
|
||||
progressWrapper.lastSentSeekRatio = next
|
||||
Timer {
|
||||
id: seekDebounce
|
||||
interval: 75
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
MediaService.seekByRatio(next)
|
||||
progressWrapper.lastSentSeekRatio = next
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSlider {
|
||||
id: progressSlider
|
||||
anchors.fill: parent
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0
|
||||
snapAlways: false
|
||||
enabled: MediaService.trackLength > 0 && MediaService.canSeek
|
||||
heightRatio: 0.6
|
||||
NSlider {
|
||||
id: progressSlider
|
||||
anchors.fill: parent
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0
|
||||
snapAlways: false
|
||||
enabled: MediaService.trackLength > 0 && MediaService.canSeek
|
||||
heightRatio: 0.6
|
||||
|
||||
onMoved: {
|
||||
progressWrapper.localSeekRatio = value
|
||||
seekDebounce.restart()
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
MediaService.isSeeking = true
|
||||
onMoved: {
|
||||
progressWrapper.localSeekRatio = value
|
||||
MediaService.seekByRatio(value)
|
||||
progressWrapper.lastSentSeekRatio = value
|
||||
} else {
|
||||
seekDebounce.stop()
|
||||
MediaService.seekByRatio(value)
|
||||
MediaService.isSeeking = false
|
||||
progressWrapper.localSeekRatio = -1
|
||||
progressWrapper.lastSentSeekRatio = -1
|
||||
seekDebounce.restart()
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
MediaService.isSeeking = true
|
||||
progressWrapper.localSeekRatio = value
|
||||
MediaService.seekByRatio(value)
|
||||
progressWrapper.lastSentSeekRatio = value
|
||||
} else {
|
||||
seekDebounce.stop()
|
||||
MediaService.seekByRatio(value)
|
||||
MediaService.isSeeking = false
|
||||
progressWrapper.localSeekRatio = -1
|
||||
progressWrapper.lastSentSeekRatio = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: progressSlider
|
||||
property: "value"
|
||||
value: progressWrapper.progressRatio
|
||||
when: !MediaService.isSeeking
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: progressSlider
|
||||
property: "value"
|
||||
value: progressWrapper.progressRatio
|
||||
when: !MediaService.isSeeking
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer to push media controls down
|
||||
Item {
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
// Media controls
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
NIconButton {
|
||||
icon: "media-prev"
|
||||
visible: MediaService.canGoPrevious
|
||||
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
|
||||
// Spacer to push media controls down
|
||||
Item {
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
visible: (MediaService.canPlay || MediaService.canPause)
|
||||
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
|
||||
}
|
||||
// Media controls
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
NIconButton {
|
||||
icon: "media-next"
|
||||
visible: MediaService.canGoNext
|
||||
onClicked: MediaService.canGoNext ? MediaService.next() : {}
|
||||
NIconButton {
|
||||
icon: "media-prev"
|
||||
visible: MediaService.canGoPrevious
|
||||
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: MediaService.isPlaying ? "media-pause" : "media-play"
|
||||
visible: (MediaService.canPlay || MediaService.canPause)
|
||||
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "media-next"
|
||||
visible: MediaService.canGoNext
|
||||
onClicked: MediaService.canGoNext ? MediaService.next() : {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +134,10 @@ NBox {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: !weatherReady
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
NBusyIndicator {}
|
||||
Loader {
|
||||
active: !weatherReady
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
sourceComponent: NBusyIndicator {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,16 +144,14 @@ SmartPanel {
|
||||
property string notificationId: model.id
|
||||
property bool isExpanded: notificationList.expandedId === notificationId
|
||||
|
||||
width: notificationList.width
|
||||
height: notificationLayoutWrapper.height + (Style.marginM * 2)
|
||||
// Cache the content height to break binding loops
|
||||
property real contentHeight: notificationLayout.implicitHeight
|
||||
|
||||
Behavior on height {
|
||||
enabled: !Settings.data.general.animationDisabled
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
// Cache truncation state to avoid binding to truncated property during polish
|
||||
property bool hasTextTruncated: false
|
||||
|
||||
width: notificationList.width
|
||||
height: contentHeight + (Style.marginM * 2)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -176,7 +174,7 @@ SmartPanel {
|
||||
anchors.fill: parent
|
||||
// Don't capture clicks on the delete button
|
||||
anchors.rightMargin: 48
|
||||
enabled: (summaryText.truncated || bodyText.truncated)
|
||||
enabled: hasTextTruncated
|
||||
onClicked: {
|
||||
if (notificationList.expandedId === notificationId) {
|
||||
notificationList.expandedId = ""
|
||||
@@ -187,6 +185,25 @@ SmartPanel {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
// Update truncation state asynchronously
|
||||
Connections {
|
||||
target: summaryText
|
||||
function onTruncatedChanged() {
|
||||
hasTextTruncated = summaryText.truncated || bodyText.truncated
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: bodyText
|
||||
function onTruncatedChanged() {
|
||||
hasTextTruncated = summaryText.truncated || bodyText.truncated
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
hasTextTruncated = summaryText.truncated || bodyText.truncated
|
||||
}
|
||||
|
||||
Item {
|
||||
id: notificationLayoutWrapper
|
||||
anchors.left: parent.left
|
||||
@@ -200,22 +217,16 @@ SmartPanel {
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
// Icon column - use simple Item instead of ColumnLayout to avoid polish loop
|
||||
Item {
|
||||
// Icon - properly centered vertically
|
||||
NImageCircled {
|
||||
Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
NImageCircled {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 20
|
||||
width: Math.round(40 * Style.uiScaleRatio)
|
||||
height: Math.round(40 * Style.uiScaleRatio)
|
||||
imagePath: model.cachedImage || model.originalImage || ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
fallbackIcon: "bell"
|
||||
fallbackIconSize: 24
|
||||
}
|
||||
Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
imagePath: model.cachedImage || model.originalImage || ""
|
||||
borderColor: Color.transparent
|
||||
borderWidth: 0
|
||||
fallbackIcon: "bell"
|
||||
fallbackIconSize: 24
|
||||
}
|
||||
|
||||
// Notification content column
|
||||
@@ -276,6 +287,11 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: isExpanded ? 999 : 2
|
||||
elide: Text.ElideRight
|
||||
|
||||
// Smooth transition without triggering layout recalculation
|
||||
Behavior on maximumLineCount {
|
||||
enabled: false // Disable animation on this to avoid polish loops
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
@@ -290,18 +306,24 @@ SmartPanel {
|
||||
maximumLineCount: isExpanded ? 999 : 3
|
||||
elide: Text.ElideRight
|
||||
visible: text.length > 0
|
||||
|
||||
// Smooth transition without triggering layout recalculation
|
||||
Behavior on maximumLineCount {
|
||||
enabled: false // Disable animation on this to avoid polish loops
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer for expand indicator
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: (!isExpanded && (summaryText.truncated || bodyText.truncated)) ? (Style.marginS) : 0
|
||||
Layout.preferredHeight: Style.marginS
|
||||
visible: !isExpanded && hasTextTruncated
|
||||
}
|
||||
|
||||
// Expand indicator
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: !isExpanded && (summaryText.truncated || bodyText.truncated)
|
||||
visible: !isExpanded && hasTextTruncated
|
||||
spacing: Style.marginXS
|
||||
|
||||
Item {
|
||||
|
||||
@@ -12,13 +12,13 @@ import qs.Modules.MainScreen
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: 820 * Style.uiScaleRatio
|
||||
preferredHeight: 900 * Style.uiScaleRatio
|
||||
preferredWidth: Math.round(820 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(900 * Style.uiScaleRatio)
|
||||
|
||||
readonly property bool attachToBar: Settings.data.ui.settingsPanelAttachToBar
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
forceAttachToBar: attachToBar
|
||||
|
||||
panelAnchorHorizontalCenter: attachToBar ? (barPosition === "top" || barPosition === "bottom") : true
|
||||
panelAnchorVerticalCenter: attachToBar ? (barPosition === "left" || barPosition === "right") : true
|
||||
panelAnchorTop: attachToBar && barPosition === "top"
|
||||
@@ -360,92 +360,93 @@ SmartPanel {
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusM
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // Don't interfere with clicks
|
||||
property int wheelAccumulator: 0
|
||||
onWheel: wheel => {
|
||||
wheelAccumulator += wheel.angleDelta.y
|
||||
if (wheelAccumulator >= 120) {
|
||||
root.selectPreviousTab()
|
||||
wheelAccumulator = 0
|
||||
} else if (wheelAccumulator <= -120) {
|
||||
root.selectNextTab()
|
||||
wheelAccumulator = 0
|
||||
}
|
||||
wheel.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
NListView {
|
||||
id: sidebarList
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
model: root.tabsModel
|
||||
spacing: Style.marginXS
|
||||
currentIndex: root.currentTabIndex
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
|
||||
Repeater {
|
||||
id: sections
|
||||
model: root.tabsModel
|
||||
delegate: Rectangle {
|
||||
id: tabItem
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: tabEntryRow.implicitHeight + Style.marginM * 2
|
||||
radius: Style.radiusS
|
||||
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : Color.transparent)
|
||||
readonly property bool selected: index === currentTabIndex
|
||||
property bool hovering: false
|
||||
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnHover : Color.mOnSurface)
|
||||
delegate: Rectangle {
|
||||
id: tabItem
|
||||
width: sidebarList.verticalScrollBarActive ? sidebarList.width - sidebarList.scrollBarWidth - Style.marginXS : sidebarList.width
|
||||
height: tabEntryRow.implicitHeight + Style.marginM * 2
|
||||
radius: Style.radiusS
|
||||
color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mHover : Color.transparent)
|
||||
readonly property bool selected: index === root.currentTabIndex
|
||||
property bool hovering: false
|
||||
property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnHover : Color.mOnSurface)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on tabTextColor {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: tabEntryRow
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS
|
||||
anchors.rightMargin: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
// Tab icon
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
color: tabTextColor
|
||||
pointSize: Style.fontSizeXL
|
||||
}
|
||||
|
||||
Behavior on tabTextColor {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
// Tab label
|
||||
NText {
|
||||
text: I18n.tr(modelData.label)
|
||||
color: tabTextColor
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: tabEntryRow
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS
|
||||
anchors.rightMargin: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
// Tab icon
|
||||
NIcon {
|
||||
icon: modelData.icon
|
||||
color: tabTextColor
|
||||
pointSize: Style.fontSizeXL
|
||||
}
|
||||
|
||||
// Tab label
|
||||
NText {
|
||||
text: I18n.tr(modelData.label)
|
||||
color: tabTextColor
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: tabItem.hovering = true
|
||||
onExited: tabItem.hovering = false
|
||||
onCanceled: tabItem.hovering = false
|
||||
onClicked: currentTabIndex = index
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: tabItem.hovering = true
|
||||
onExited: tabItem.hovering = false
|
||||
onCanceled: tabItem.hovering = false
|
||||
onClicked: root.currentTabIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex !== root.currentTabIndex) {
|
||||
root.currentTabIndex = currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onCurrentTabIndexChanged() {
|
||||
if (sidebarList.currentIndex !== root.currentTabIndex) {
|
||||
sidebarList.currentIndex = root.currentTabIndex
|
||||
sidebarList.positionViewAtIndex(root.currentTabIndex, ListView.Contain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ ColumnLayout {
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["left"]
|
||||
@@ -274,7 +274,7 @@ ColumnLayout {
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["right"]
|
||||
|
||||
@@ -11,8 +11,8 @@ import qs.Modules.MainScreen
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: Math.round(520 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(600 * Style.uiScaleRatio)
|
||||
preferredWidth: Math.round(preferredWidthRatio * 2560 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(preferredHeightRatio * 1440 * Style.uiScaleRatio)
|
||||
preferredWidthRatio: 0.4
|
||||
preferredHeightRatio: 0.6
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ Singleton {
|
||||
},
|
||||
"SystemMonitor": {
|
||||
"allowUserSettings": true,
|
||||
"usePrimaryColor": true,
|
||||
"usePrimaryColor": false,
|
||||
"showCpuUsage": true,
|
||||
"showCpuTemp": true,
|
||||
"showMemoryUsage": true,
|
||||
|
||||
+13
-1
@@ -40,7 +40,7 @@ Item {
|
||||
function toggle() {
|
||||
root.withTargetScreen(screen => {
|
||||
var calendarPanel = PanelService.getPanel("calendarPanel", screen)
|
||||
calendarPanel.toggle(null, "Clock")
|
||||
calendarPanel?.toggle(null, "Clock")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,6 +287,18 @@ Item {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function toggleNoctaliaPerformance() {
|
||||
PowerProfileService.toggleNoctaliaPerformance()
|
||||
}
|
||||
|
||||
function enableNoctaliaPerformance() {
|
||||
PowerProfileService.setNoctaliaPerformance(true)
|
||||
}
|
||||
|
||||
function disableNoctaliaPerformance() {
|
||||
PowerProfileService.setNoctaliaPerformance(false)
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
|
||||
@@ -38,6 +38,8 @@ Singleton {
|
||||
var lines = []
|
||||
lines.push("[config]")
|
||||
lines.push("")
|
||||
lines.push("[templates]")
|
||||
lines.push("")
|
||||
lines.push("# User-defined templates")
|
||||
lines.push("# Add your custom templates below")
|
||||
lines.push("# Example:")
|
||||
|
||||
@@ -123,7 +123,7 @@ Singleton {
|
||||
const data = createData(notification)
|
||||
addToHistory(data)
|
||||
|
||||
if (Settings.data.notifications?.doNotDisturb)
|
||||
if (Settings.data.notifications?.doNotDisturb || PowerProfileService.noctaliaPerformanceMode)
|
||||
return
|
||||
|
||||
activeMap[data.id] = notification
|
||||
|
||||
@@ -13,6 +13,9 @@ Singleton {
|
||||
readonly property bool available: powerProfiles && powerProfiles.hasPerformanceProfile
|
||||
property int profile: powerProfiles ? powerProfiles.profile : PowerProfile.Balanced
|
||||
|
||||
// Not a power profile but a volatile property to quickly disable shadows, animations, etc..
|
||||
property bool noctaliaPerformanceMode: false
|
||||
|
||||
function getName(p) {
|
||||
if (!available)
|
||||
return "Unknown"
|
||||
@@ -94,4 +97,24 @@ Singleton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Noctalia Performance Mode
|
||||
// - Turning shadow off
|
||||
// - Turning animation off
|
||||
// - Do Not Disturb
|
||||
function toggleNoctaliaPerformance() {
|
||||
noctaliaPerformanceMode = !noctaliaPerformanceMode
|
||||
}
|
||||
|
||||
function setNoctaliaPerformance(value) {
|
||||
noctaliaPerformanceMode = value
|
||||
}
|
||||
|
||||
onNoctaliaPerformanceModeChanged: {
|
||||
if (noctaliaPerformanceMode) {
|
||||
ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.enabled"), "seedling")
|
||||
} else {
|
||||
ToastService.showNotice(I18n.tr("toast.noctalia-performance.label"), I18n.tr("toast.noctalia-performance.disabled"), "seedling-off")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property string baseVersion: "3.0.0"
|
||||
property string baseVersion: "3.0.2"
|
||||
property bool isDevelopment: true
|
||||
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
|
||||
+35
-25
@@ -14,40 +14,50 @@ Item {
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
// GPU-optimized spinner - draw once, rotate with GPU transform
|
||||
Item {
|
||||
id: spinner
|
||||
anchors.fill: parent
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
// Static canvas - drawn ONCE, then cached
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
renderStrategy: Canvas.Cooperative // Better performance than Threaded for simple shapes
|
||||
renderTarget: Canvas.FramebufferObject // GPU texture
|
||||
|
||||
var centerX = width / 2
|
||||
var centerY = height / 2
|
||||
var radius = Math.min(width, height) / 2 - strokeWidth / 2
|
||||
// Enable layer caching - critical for performance!
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
ctx.strokeStyle = root.color
|
||||
ctx.lineWidth = Math.max(1, root.strokeWidth)
|
||||
ctx.lineCap = "round"
|
||||
Component.onCompleted: {
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
// Draw arc with gap (270 degrees with 90 degree gap)
|
||||
ctx.beginPath()
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2 + rotationAngle, -Math.PI / 2 + rotationAngle + Math.PI * 1.5)
|
||||
ctx.stroke()
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
|
||||
var centerX = width / 2
|
||||
var centerY = height / 2
|
||||
var radius = Math.min(width, height) / 2 - strokeWidth / 2
|
||||
|
||||
ctx.strokeStyle = root.color
|
||||
ctx.lineWidth = Math.max(1, root.strokeWidth)
|
||||
ctx.lineCap = "round"
|
||||
|
||||
// Draw arc with gap (270 degrees = 3/4 of circle)
|
||||
ctx.beginPath()
|
||||
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + Math.PI * 1.5)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
property real rotationAngle: 0
|
||||
|
||||
onRotationAngleChanged: {
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: canvas
|
||||
property: "rotationAngle"
|
||||
// Smooth rotation animation - uses GPU transform, NO canvas repaints!
|
||||
RotationAnimation on rotation {
|
||||
running: root.running
|
||||
from: 0
|
||||
to: 2 * Math.PI
|
||||
to: 360
|
||||
duration: root.duration
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
+35
-5
@@ -24,8 +24,27 @@ Rectangle {
|
||||
border.color: flat ? Color.transparent : Color.mSurfaceVariant
|
||||
border.width: flat ? 0 : Style.borderS
|
||||
|
||||
// Repaint gauge when the bound value changes
|
||||
onValueChanged: gauge.requestPaint()
|
||||
// Animated value for smooth transitions - reduces repaint frequency
|
||||
property real animatedValue: value
|
||||
|
||||
Behavior on animatedValue {
|
||||
enabled: !Settings.data.general.animationDisabled
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Repaint gauge when animated value changes (throttled by animation)
|
||||
onAnimatedValueChanged: repaintTimer.restart()
|
||||
|
||||
// Debounce timer to limit repaint frequency during rapid value changes
|
||||
Timer {
|
||||
id: repaintTimer
|
||||
interval: 16 // ~60 FPS max
|
||||
repeat: false
|
||||
onTriggered: gauge.requestPaint()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
@@ -45,7 +64,18 @@ Rectangle {
|
||||
Canvas {
|
||||
id: gauge
|
||||
anchors.fill: parent
|
||||
renderStrategy: Canvas.Immediate
|
||||
|
||||
// Optimized Canvas settings for better GPU performance
|
||||
renderStrategy: Canvas.Cooperative // Better performance than Immediate
|
||||
renderTarget: Canvas.FramebufferObject // GPU texture rendering
|
||||
|
||||
// Enable layer caching - critical for performance!
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
Component.onCompleted: {
|
||||
requestPaint()
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
@@ -68,7 +98,7 @@ Rectangle {
|
||||
ctx.stroke()
|
||||
|
||||
// Value arc with gradient starting at 25%
|
||||
const ratio = Math.max(0, Math.min(1, root.value / 100))
|
||||
const ratio = Math.max(0, Math.min(1, root.animatedValue / 100))
|
||||
const end = start + (endBg - start) * ratio
|
||||
|
||||
// Calculate gradient start point (25% into the arc)
|
||||
@@ -97,7 +127,7 @@ Rectangle {
|
||||
id: valueLabel
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: -4 * contentScale
|
||||
text: `${root.value}${root.suffix}`
|
||||
text: `${Math.round(root.value)}${root.suffix}`
|
||||
pointSize: Style.fontSizeM * contentScale * 0.9
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
// Unified shadow system
|
||||
Item {
|
||||
id: root
|
||||
required property var source
|
||||
|
||||
layer.enabled: Settings.data.general.enableShadows
|
||||
layer.enabled: Settings.data.general.enableShadows && !PowerProfileService.noctaliaPerformanceMode
|
||||
layer.effect: MultiEffect {
|
||||
source: root.source
|
||||
shadowEnabled: true
|
||||
|
||||
+7
-46
@@ -13,7 +13,13 @@ Item {
|
||||
property real handleWidth: 6
|
||||
property real handleRadius: Style.radiusM
|
||||
property int verticalPolicy: ScrollBar.AsNeeded
|
||||
property int horizontalPolicy: ScrollBar.AsNeeded
|
||||
property int horizontalPolicy: ScrollBar.AlwaysOff
|
||||
readonly property bool verticalScrollBarActive: {
|
||||
if (listView.ScrollBar.vertical.policy === ScrollBar.AlwaysOff)
|
||||
return false
|
||||
return listView.contentHeight > listView.height
|
||||
}
|
||||
readonly property real scrollBarWidth: verticalScrollBarActive ? handleWidth : 0
|
||||
|
||||
// Forward ListView properties
|
||||
property alias model: listView.model
|
||||
@@ -118,7 +124,6 @@ Item {
|
||||
x: listView.mirrored ? 0 : listView.width - width
|
||||
y: 0
|
||||
height: listView.height
|
||||
active: listView.ScrollBar.horizontal.active
|
||||
policy: root.verticalPolicy
|
||||
|
||||
contentItem: Rectangle {
|
||||
@@ -155,49 +160,5 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.horizontal: ScrollBar {
|
||||
id: horizontalScrollBar
|
||||
parent: listView
|
||||
x: 0
|
||||
y: listView.height - height
|
||||
width: listView.width
|
||||
active: listView.ScrollBar.vertical.active
|
||||
policy: root.horizontalPolicy
|
||||
|
||||
contentItem: Rectangle {
|
||||
implicitWidth: 100
|
||||
implicitHeight: root.handleWidth
|
||||
radius: root.handleRadius
|
||||
color: parent.pressed ? root.handlePressedColor : parent.hovered ? root.handleHoverColor : root.handleColor
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 100
|
||||
implicitHeight: root.handleWidth
|
||||
color: root.trackColor
|
||||
opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0
|
||||
radius: root.handleRadius / 2
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Corner radius properties
|
||||
property real topLeftRadius: 0
|
||||
property real topRightRadius: 0
|
||||
property real bottomLeftRadius: 0
|
||||
property real bottomRightRadius: 0
|
||||
|
||||
// Inverted corner properties (concave instead of convex)
|
||||
property bool topLeftInverted: false
|
||||
property bool topRightInverted: false
|
||||
property bool bottomLeftInverted: false
|
||||
property bool bottomRightInverted: false
|
||||
|
||||
// Direction for inverted corners: "horizontal" or "vertical"
|
||||
// horizontal: curves left/right (extends beyond left/right edges)
|
||||
// vertical: curves up/down (extends beyond top/bottom edges)
|
||||
property string topLeftInvertedDirection: "horizontal" // default: curves left
|
||||
property string topRightInvertedDirection: "horizontal" // default: curves right
|
||||
property string bottomLeftInvertedDirection: "horizontal" // default: curves left
|
||||
property string bottomRightInvertedDirection: "horizontal" // default: curves right
|
||||
|
||||
// Background color and borders
|
||||
property color backgroundColor: Color.mPrimary
|
||||
property color borderColor: Color.mOutline
|
||||
property int borderWidth: Style.borderS
|
||||
|
||||
// Check if any corner is inverted
|
||||
readonly property bool hasInvertedCorners: topLeftInverted || topRightInverted || bottomLeftInverted || bottomRightInverted
|
||||
|
||||
// Calculate padding needed for inverted corners based on their direction
|
||||
readonly property real topPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "vertical") ? topLeftRadius : 0, (topRightInverted && topRightInvertedDirection === "vertical") ? topRightRadius : 0)
|
||||
readonly property real bottomPadding: Math.max((bottomLeftInverted && bottomLeftInvertedDirection === "vertical") ? bottomLeftRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "vertical") ? bottomRightRadius : 0)
|
||||
readonly property real leftPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "horizontal") ? topLeftRadius : 0, (bottomLeftInverted && bottomLeftInvertedDirection === "horizontal") ? bottomLeftRadius : 0)
|
||||
readonly property real rightPadding: Math.max((topRightInverted && topRightInvertedDirection === "horizontal") ? topRightRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "horizontal") ? bottomRightRadius : 0)
|
||||
|
||||
// Background layer
|
||||
Item {
|
||||
id: backgroundLayer
|
||||
anchors.fill: parent
|
||||
z: 0
|
||||
|
||||
// Simple rectangle for non-inverted corners (better performance)
|
||||
Rectangle {
|
||||
id: simpleBackground
|
||||
anchors.fill: parent
|
||||
color: root.backgroundColor
|
||||
radius: topLeftRadius // Use topLeftRadius as default
|
||||
border.width: borderWidth
|
||||
border.color: borderColor
|
||||
visible: !root.hasInvertedCorners
|
||||
|
||||
topLeftRadius: root.topLeftRadius
|
||||
topRightRadius: root.topRightRadius
|
||||
bottomLeftRadius: root.bottomLeftRadius
|
||||
bottomRightRadius: root.bottomRightRadius
|
||||
}
|
||||
|
||||
// Background with custom corners (for inverted corners)
|
||||
Canvas {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: -root.topPadding
|
||||
anchors.bottomMargin: -root.bottomPadding
|
||||
anchors.leftMargin: -root.leftPadding
|
||||
anchors.rightMargin: -root.rightPadding
|
||||
visible: root.hasInvertedCorners
|
||||
|
||||
antialiasing: true
|
||||
renderTarget: Canvas.FramebufferObject
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
|
||||
// Adjust coordinates to account for inverted corner padding
|
||||
var x = root.leftPadding
|
||||
var y = root.topPadding
|
||||
var w = width - root.leftPadding - root.rightPadding
|
||||
var h = height - root.topPadding - root.bottomPadding
|
||||
|
||||
ctx.fillStyle = root.backgroundColor
|
||||
ctx.beginPath()
|
||||
|
||||
// Start from top left
|
||||
if (topLeftInverted) {
|
||||
if (topLeftInvertedDirection === "vertical") {
|
||||
ctx.moveTo(x, y)
|
||||
} else {
|
||||
ctx.moveTo(x + topLeftRadius, y)
|
||||
}
|
||||
} else {
|
||||
ctx.moveTo(x + topLeftRadius, y)
|
||||
}
|
||||
|
||||
// Top edge and top right corner
|
||||
if (topRightInverted) {
|
||||
if (topRightInvertedDirection === "horizontal") {
|
||||
// Curves to the right
|
||||
ctx.lineTo(x + w, y)
|
||||
ctx.lineTo(x + w + topRightRadius, y)
|
||||
ctx.quadraticCurveTo(x + w, y, x + w, y + topRightRadius)
|
||||
} else {
|
||||
// Curves upward
|
||||
ctx.lineTo(x + w, y)
|
||||
ctx.lineTo(x + w, y - topRightRadius)
|
||||
ctx.quadraticCurveTo(x + w, y, x + w - topRightRadius, y)
|
||||
ctx.lineTo(x + w, y)
|
||||
ctx.lineTo(x + w, y + topRightRadius)
|
||||
}
|
||||
} else {
|
||||
ctx.lineTo(x + w - topRightRadius, y)
|
||||
ctx.arcTo(x + w, y, x + w, y + topRightRadius, topRightRadius)
|
||||
}
|
||||
|
||||
// Right edge and bottom right corner
|
||||
if (bottomRightInverted) {
|
||||
if (bottomRightInvertedDirection === "horizontal") {
|
||||
// Curves to the right
|
||||
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w + bottomRightRadius, y + h)
|
||||
ctx.lineTo(x + w, y + h)
|
||||
ctx.lineTo(x + w - bottomRightRadius, y + h)
|
||||
} else {
|
||||
// Curves downward
|
||||
ctx.lineTo(x + w, y + h)
|
||||
ctx.lineTo(x + w, y + h + bottomRightRadius)
|
||||
ctx.quadraticCurveTo(x + w, y + h, x + w - bottomRightRadius, y + h)
|
||||
}
|
||||
} else {
|
||||
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
||||
ctx.arcTo(x + w, y + h, x + w - bottomRightRadius, y + h, bottomRightRadius)
|
||||
}
|
||||
|
||||
// Bottom edge and bottom left corner
|
||||
if (bottomLeftInverted) {
|
||||
if (bottomLeftInvertedDirection === "horizontal") {
|
||||
// Curves to the left
|
||||
ctx.lineTo(x + bottomLeftRadius, y + h)
|
||||
ctx.lineTo(x - bottomLeftRadius, y + h)
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h - bottomLeftRadius)
|
||||
} else {
|
||||
// Curves downward
|
||||
ctx.lineTo(x + bottomLeftRadius, y + h)
|
||||
ctx.quadraticCurveTo(x, y + h, x, y + h + bottomLeftRadius)
|
||||
ctx.lineTo(x, y + h + bottomLeftRadius)
|
||||
}
|
||||
} else {
|
||||
ctx.lineTo(x + bottomLeftRadius, y + h)
|
||||
ctx.arcTo(x, y + h, x, y + h - bottomLeftRadius, bottomLeftRadius)
|
||||
}
|
||||
|
||||
// Left edge and back to top left corner
|
||||
if (topLeftInverted) {
|
||||
if (topLeftInvertedDirection === "horizontal") {
|
||||
// Curves to the left
|
||||
ctx.lineTo(x, y + topLeftRadius)
|
||||
ctx.quadraticCurveTo(x, y, x - topLeftRadius, y)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.lineTo(x + topLeftRadius, y)
|
||||
} else {
|
||||
// Curves upward
|
||||
ctx.lineTo(x, y + topLeftRadius)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.lineTo(x, y - topLeftRadius)
|
||||
ctx.quadraticCurveTo(x, y, x + topLeftRadius, y)
|
||||
}
|
||||
} else {
|
||||
ctx.lineTo(x, y + topLeftRadius)
|
||||
ctx.arcTo(x, y, x + topLeftRadius, y, topLeftRadius)
|
||||
}
|
||||
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content layer: for child elements
|
||||
default property alias contentChildren: contentLayer.data
|
||||
Item {
|
||||
id: contentLayer
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
}
|
||||
|
||||
// Trigger repaint when properties change
|
||||
onTopLeftRadiusChanged: background.requestPaint()
|
||||
onTopRightRadiusChanged: background.requestPaint()
|
||||
onBottomLeftRadiusChanged: background.requestPaint()
|
||||
onBottomRightRadiusChanged: background.requestPaint()
|
||||
onTopLeftInvertedChanged: background.requestPaint()
|
||||
onTopRightInvertedChanged: background.requestPaint()
|
||||
onBottomLeftInvertedChanged: background.requestPaint()
|
||||
onBottomRightInvertedChanged: background.requestPaint()
|
||||
onTopLeftInvertedDirectionChanged: background.requestPaint()
|
||||
onTopRightInvertedDirectionChanged: background.requestPaint()
|
||||
onBottomLeftInvertedDirectionChanged: background.requestPaint()
|
||||
onBottomRightInvertedDirectionChanged: background.requestPaint()
|
||||
onBackgroundColorChanged: background.requestPaint()
|
||||
onWidthChanged: background.requestPaint()
|
||||
onHeightChanged: background.requestPaint()
|
||||
}
|
||||
+5
-28
@@ -7,6 +7,7 @@ import qs.Services
|
||||
Slider {
|
||||
id: root
|
||||
|
||||
property color fillColor: Color.mPrimary
|
||||
property var cutoutColor: Color.mSurface
|
||||
property bool snapAlways: true
|
||||
property real heightRatio: 0.7
|
||||
@@ -46,7 +47,7 @@ Slider {
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
radius: width / 2
|
||||
color: Qt.darker(Color.mPrimary, 1.2) //starting color of gradient
|
||||
color: Qt.darker(fillColor, 1.2) //starting color of gradient
|
||||
}
|
||||
|
||||
// The main rectangle
|
||||
@@ -60,35 +61,11 @@ Slider {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Qt.darker(Color.mPrimary, 1.2)
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.5
|
||||
color: Color.mPrimary
|
||||
SequentialAnimation on position {
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
from: 0.3
|
||||
to: 0.7
|
||||
duration: 2000
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
NumberAnimation {
|
||||
from: 0.7
|
||||
to: 0.3
|
||||
duration: 2000
|
||||
easing.type: Easing.InOutSine
|
||||
}
|
||||
}
|
||||
color: Qt.darker(fillColor, 1.2)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: Qt.lighter(Color.mPrimary, 1.2)
|
||||
color: fillColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +95,7 @@ Slider {
|
||||
implicitHeight: knobDiameter
|
||||
radius: width / 2
|
||||
color: root.pressed ? Color.mHover : Color.mSurface
|
||||
border.color: Color.mPrimary
|
||||
border.color: fillColor
|
||||
border.width: Style.borderL
|
||||
anchors.centerIn: parent
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ ShellRoot {
|
||||
IdleInhibitorService.init()
|
||||
PowerProfileService.init()
|
||||
DistroService.init()
|
||||
|
||||
// Only open the setup wizard for new users
|
||||
if (!Settings.data.setupCompleted) {
|
||||
checkSetupWizard()
|
||||
}
|
||||
}
|
||||
|
||||
Overview {}
|
||||
@@ -96,18 +101,7 @@ ShellRoot {
|
||||
IPCService {}
|
||||
|
||||
// MainScreen for each screen (manages bar + all panels)
|
||||
MainScreens {}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Wizard - Auto Kick start
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsLoaded() {
|
||||
// Only open the setup wizard for new users
|
||||
if (!Settings.data.setupCompleted) {
|
||||
checkSetupWizard()
|
||||
}
|
||||
AllScreens {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,23 +119,25 @@ ShellRoot {
|
||||
}
|
||||
|
||||
if (Settings.data.settingsVersion >= Settings.settingsVersion) {
|
||||
// Open Setup Wizard as a panel in the same windowing system as Settings/ControlCenter
|
||||
if (Quickshell.screens.length > 0) {
|
||||
var targetScreen = Quickshell.screens[0]
|
||||
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen)
|
||||
if (setupPanel) {
|
||||
setupPanel.open()
|
||||
} else {
|
||||
// If not yet loaded, ensure it loads and try again shortly
|
||||
Qt.callLater(() => {
|
||||
var sp = PanelService.getPanel("setupWizardPanel", targetScreen)
|
||||
if (sp)
|
||||
sp.open()
|
||||
})
|
||||
}
|
||||
}
|
||||
showSetupWizard()
|
||||
} else {
|
||||
Settings.data.setupCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
function showSetupWizard() {
|
||||
// Open Setup Wizard as a panel in the same windowing system as Settings/ControlCenter
|
||||
if (Quickshell.screens.length > 0) {
|
||||
var targetScreen = Quickshell.screens[0]
|
||||
var setupPanel = PanelService.getPanel("setupWizardPanel", targetScreen)
|
||||
if (setupPanel) {
|
||||
setupPanel.open()
|
||||
} else {
|
||||
// If not yet loaded, ensure it loads and try again shortly
|
||||
Qt.callLater(() => {
|
||||
showSetupWizard()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user