Merge branch 'main' into fix/calendar-alignment

This commit is contained in:
MrDowntempo
2025-10-11 14:30:34 -04:00
committed by GitHub
30 changed files with 596 additions and 257 deletions
+44 -55
View File
@@ -44,19 +44,6 @@ Variants {
property real fillMode: WallpaperService.getFillModeUniform()
property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0)
property int monitoredWidth: modelData.width
property int monitoredHeight: modelData.height
onMonitoredWidthChanged: {
Logger.log("Background", "Screen width changed to:", monitoredWidth, "for", modelData.name)
recalculateImageSizes()
}
onMonitoredHeightChanged: {
Logger.log("Background", "Screen height changed to:", monitoredHeight, "for", modelData.name)
recalculateImageSizes()
}
Component.onCompleted: setWallpaperInitial()
Component.onDestruction: {
@@ -87,6 +74,13 @@ Variants {
}
}
Connections {
target: CompositorService
function onDisplayScalesChanged() {
setWallpaperInitial()
}
}
color: Color.transparent
screen: modelData
WlrLayershell.layer: WlrLayer.Background
@@ -122,38 +116,21 @@ Variants {
cache: false
asynchronous: true
sourceSize: undefined
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("Current wallpaper failed to load:", source)
} else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true
calculateSourceSize()
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
if (optimalSize !== false) {
sourceSize = optimalSize
}
}
}
onSourceChanged: {
dimensionsCalculated = false
sourceSize = undefined
}
function calculateSourceSize() {
if (implicitWidth === modelData.width || implicitHeight === modelData.height) {
// Do not resize if one of the dimensions fits perfectly on the screen
return
}
if (implicitWidth > 0 && implicitHeight > 0) {
const imageAspectRatio = implicitWidth / implicitHeight
if (modelData.width >= modelData.height) {
const w = Math.min(modelData.width, implicitWidth)
sourceSize = Qt.size(w, w / imageAspectRatio)
} else {
const h = Math.min(modelData.height, implicitHeight)
sourceSize = Qt.size(h * imageAspectRatio, h)
}
}
}
}
Image {
@@ -168,38 +145,21 @@ Variants {
cache: false
asynchronous: true
sourceSize: undefined
onStatusChanged: {
if (status === Image.Error) {
Logger.warn("Next wallpaper failed to load:", source)
} else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true
calculateSourceSize()
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
if (optimalSize !== false) {
sourceSize = optimalSize
}
}
}
onSourceChanged: {
dimensionsCalculated = false
sourceSize = undefined
}
function calculateSourceSize() {
if (implicitWidth === modelData.width || implicitHeight === modelData.height) {
// Do not resize if one of the dimensions fits perfectly on the screen
return
}
if (implicitWidth > 0 && implicitHeight > 0) {
const imageAspectRatio = implicitWidth / implicitHeight
if (modelData.width >= modelData.height) {
const w = Math.min(modelData.width, implicitWidth)
sourceSize = Qt.size(w, w / imageAspectRatio)
} else {
const h = Math.min(modelData.height, implicitHeight)
sourceSize = Qt.size(h * imageAspectRatio, h)
}
}
}
}
// Dynamic shader loader - only loads the active transition shader
@@ -356,6 +316,31 @@ Variants {
}
}
// ------------------------------------------------------
function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
const compositorScale = CompositorService.getDisplayScale(modelData.name)
const screenWidth = modelData.width * compositorScale
const screenHeight = modelData.height * compositorScale
if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
// Do not resize if wallpaper is smaller than one of the screen dimension
return
}
const imageAspectRatio = wpWidth / wpHeight
var dim = Qt.size(0, 0)
if (screenWidth >= screenHeight) {
const w = Math.min(screenWidth, wpWidth)
dim = Qt.size(w, w / imageAspectRatio)
} else {
const h = Math.min(screenHeight, wpHeight)
dim = Qt.size(h * imageAspectRatio, h)
}
Logger.log("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height)
return dim
}
// ------------------------------------------------------
function recalculateImageSizes() {
if (currentWallpaper.status === Image.Ready) {
currentWallpaper.calculateSourceSize()
@@ -365,6 +350,7 @@ Variants {
}
}
// ------------------------------------------------------
function setWallpaperInitial() {
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick
if (!WallpaperService || !WallpaperService.isInitialized) {
@@ -375,6 +361,7 @@ Variants {
setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name))
}
// ------------------------------------------------------
function setWallpaperImmediate(source) {
transitionAnimation.stop()
transitionProgress = 0.0
@@ -388,6 +375,7 @@ Variants {
})
}
// ------------------------------------------------------
function setWallpaperWithTransition(source) {
if (source === currentWallpaper.source) {
return
@@ -421,6 +409,7 @@ Variants {
transitionAnimation.start()
}
// ------------------------------------------------------
// Main method that actually trigger the wallpaper change
function changeWallpaper() {
// Get the transitionType from the settings
+34 -40
View File
@@ -91,7 +91,7 @@ NPanel {
}
}
// Today day number
// Today day number - with simple, stable animation
NText {
opacity: content.isCurrentMonth ? 1.0 : 0.0
Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0
@@ -104,8 +104,17 @@ NPanel {
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Behavior on opacity { NumberAnimation { duration: Style.animationFast } }
Behavior on Layout.preferredWidth { NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } }
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
// Month, year, location
@@ -163,7 +172,7 @@ NPanel {
}
}
// Spacer between date and clock
// Spacer to push content left
Item {
Layout.fillWidth: true
}
@@ -227,6 +236,26 @@ NPanel {
var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH")
return t.split(" ")[0]
}
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3 * scaling
ctx.reset()
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15)
ctx.stroke()
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5 * scaling
ctx.strokeStyle = Color.mOnPrimary
ctx.lineCap = "round"
ctx.stroke()
}
}
pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
@@ -247,13 +276,12 @@ NPanel {
}
}
// 6-day forecast (outside blue banner)
// ... (rest of the file is unchanged) ...
RowLayout {
visible: weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginL * scaling
Repeater {
model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0
delegate: ColumnLayout {
@@ -261,7 +289,6 @@ NPanel {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginS * scaling
NText {
text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
@@ -272,14 +299,12 @@ NPanel {
font.weight: Style.fontWeightMedium
Layout.alignment: Qt.AlignHCenter
}
NIcon {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
pointSize: Style.fontSizeXXL * 1.5 * scaling
color: Color.mPrimary
}
NText {
Layout.alignment: Qt.AlignHCenter
text: {
@@ -300,27 +325,19 @@ NPanel {
}
}
}
// Loading indicator for weather
RowLayout {
visible: !weatherReady
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
NBusyIndicator {}
}
// Spacer
Item {}
// Navigation and divider
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS * scaling
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
@@ -330,7 +347,6 @@ NPanel {
content.isCurrentMonth = content.checkIsCurrentMonth()
}
}
NIconButton {
icon: "calendar"
onClicked: {
@@ -339,7 +355,6 @@ NPanel {
content.isCurrentMonth = true
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
@@ -350,31 +365,24 @@ NPanel {
}
}
}
// Names of days of the week
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling
NText {
anchors.centerIn: parent
text: {
@@ -391,27 +399,20 @@ NPanel {
}
}
}
// Grid with weeks and days
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
// Column of week numbers
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0
Layout.fillHeight: true
spacing: 0
Repeater {
model: 6
Item {
Layout.fillWidth: true
Layout.fillHeight: true
NText {
anchors.centerIn: parent
color: Color.mOutline
@@ -443,27 +444,21 @@ NPanel {
}
}
}
// Days Grid
MonthGrid {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Style.marginXXS * scaling
month: Time.date.getMonth()
year: Time.date.getFullYear()
locale: Qt.locale()
delegate: Item {
Rectangle {
width: Style.baseWidgetSize * 0.9 * scaling
height: Style.baseWidgetSize * 0.9 * scaling
anchors.centerIn: parent
radius: Style.radiusM * scaling
color: model.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: model.day
@@ -478,7 +473,6 @@ NPanel {
pointSize: Style.fontSizeM * scaling
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
+2 -1
View File
@@ -9,6 +9,7 @@ import qs.Widgets
NBox {
id: root
property real scaling: 1.0
property real localOutputVolume: AudioService.volume
property real localInputVolume: AudioService.inputVolume
@@ -42,7 +43,7 @@ NBox {
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginM * scaling
spacing: 0
// Output Volume Section
ColumnLayout {
+2 -2
View File
@@ -110,7 +110,7 @@ NBox {
}
}
// Player selector - positioned at the very top
// Player selector
Rectangle {
id: playerSelectorButton
anchors.top: parent.top
@@ -306,7 +306,7 @@ NBox {
NText {
visible: MediaService.trackTitle !== ""
text: MediaService.trackTitle
pointSize: Style.fontSizeM * scaling
pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
elide: Text.ElideRight
wrapMode: Text.Wrap
@@ -9,49 +9,54 @@ import qs.Widgets
NBox {
id: root
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: Style.marginS * scaling
anchors.rightMargin: Style.marginS * scaling
anchors.topMargin: Style.marginXS * scaling
anchors.bottomMargin: Style.marginM * scaling
spacing: Style.marginS * scaling
property real scaling: 1.0
NCircleStat {
value: SystemStatService.cpuUsage
icon: "cpu-usage"
flat: true
contentScale: 0.8
width: 72 * scaling
height: 68 * scaling
}
NCircleStat {
value: SystemStatService.cpuTemp
suffix: "°C"
icon: "cpu-temperature"
flat: true
contentScale: 0.8
width: 72 * scaling
height: 68 * scaling
}
NCircleStat {
value: SystemStatService.memPercent
icon: "memory"
flat: true
contentScale: 0.8
width: 72 * scaling
height: 68 * scaling
}
NCircleStat {
value: SystemStatService.diskPercent
icon: "storage"
flat: true
contentScale: 0.8
width: 72 * scaling
height: 68 * scaling
Item {
id: content
anchors.fill: parent
anchors.margins: Style.marginS * scaling
ColumnLayout {
anchors.centerIn: parent
spacing: 0
NCircleStat {
value: SystemStatService.cpuUsage
icon: "cpu-usage"
flat: true
contentScale: 0.8
height: 65 * scaling
scaling: root.scaling
Layout.alignment: Qt.AlignHCenter
}
NCircleStat {
value: SystemStatService.cpuTemp
suffix: "°C"
icon: "cpu-temperature"
flat: true
contentScale: 0.8
height: 65 * scaling
scaling: root.scaling
Layout.alignment: Qt.AlignHCenter
}
NCircleStat {
value: SystemStatService.memPercent
icon: "memory"
flat: true
contentScale: 0.8
height: 65 * scaling
scaling: root.scaling
Layout.alignment: Qt.AlignHCenter
}
NCircleStat {
value: SystemStatService.diskPercent
icon: "storage"
flat: true
contentScale: 0.8
height: 65 * scaling
scaling: root.scaling
Layout.alignment: Qt.AlignHCenter
}
}
}
}
+1 -1
View File
@@ -85,7 +85,7 @@ NBox {
model: weatherReady ? LocationService.data.weather.daily.time : []
delegate: ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginL * scaling
spacing: Style.marginXS * scaling
NText {
text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
+11 -3
View File
@@ -11,7 +11,7 @@ NPanel {
id: root
preferredWidth: 460
preferredHeight: 734
preferredHeight: 790
panelKeyboardFocus: true
// Positioning
@@ -44,13 +44,13 @@ NPanel {
WeatherCard {
Layout.fillWidth: true
Layout.preferredHeight: Math.max(220 * scaling)
Layout.preferredHeight: Math.max(190 * scaling)
}
// Middle section: media + stats column
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: Math.max(310 * scaling)
Layout.preferredHeight: Math.max(260 * scaling)
spacing: content.cardSpacing
// Media card
@@ -63,9 +63,17 @@ NPanel {
SystemMonitorCard {
Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling
Layout.fillHeight: true
scaling: root.scaling
}
}
// Audio card below media and system monitor
AudioCard {
Layout.fillWidth: true
Layout.preferredHeight: Math.max(120 * scaling)
scaling: root.scaling
}
// Bottom actions (two grouped rows of round buttons)
RowLayout {
Layout.fillWidth: true
+2 -4
View File
@@ -192,10 +192,8 @@ NPanel {
// Reset when launcher opens
Connections {
target: root
function onOpenedChanged() {
if (root.opened) {
mouseMovementDetector.initialized = false
}
function onOpened() {
mouseMovementDetector.initialized = false
}
}
}
+41 -25
View File
@@ -41,6 +41,7 @@ Variants {
// Brightness properties
property bool brightnessInitialized: false
property int brightnessChangeCount: 0
readonly property real currentBrightness: {
if (BrightnessService.monitors.length > 0) {
return BrightnessService.monitors[0].brightness || 0
@@ -123,6 +124,9 @@ Variants {
id: panel
screen: modelData
// PanelWindow scaling
property real scaling: ScalingService.getScreenScale(screen)
readonly property string location: (Settings.data.osd && Settings.data.osd.location) ? Settings.data.osd.location : "top_right"
readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top")
readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom")
@@ -130,11 +134,12 @@ Variants {
readonly property bool isRight: (location.indexOf("_right") >= 0) || (location === "right")
readonly property bool isCentered: (location === "top" || location === "bottom")
readonly property bool verticalMode: (location === "left" || location === "right")
readonly property int hWidth: Math.round(320 * root.scaling)
readonly property int hHeight: Math.round(64 * root.scaling)
readonly property int hWidth: Math.round(320 * scaling)
readonly property int hHeight: Math.round(64 * scaling)
readonly property int vHeight: Math.round(320 * scaling) // Vertical OSD height (matches horizontal width)
// Ensure an even width to keep the vertical bar perfectly centered
readonly property int barThickness: (function () {
const base = Math.max(6, Math.round(6 * root.scaling))
const base = Math.max(8, Math.round(8 * scaling))
return (base % 2 === 0) ? base : base + 1
})()
@@ -142,6 +147,15 @@ Variants {
connectBrightnessMonitors()
}
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if ((screen !== null) && (screenName === screen.name)) {
scaling = scale
}
}
}
Component.onDestruction: {
disconnectBrightnessMonitors()
}
@@ -203,18 +217,19 @@ Variants {
color: Color.transparent
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.layer: (Settings.data.osd && Settings.data.osd.alwaysOnTop) ? WlrLayer.Overlay : WlrLayer.Top
exclusionMode: PanelWindow.ExclusionMode.Ignore
Rectangle {
id: osdItem
width: parent.width
height: panel.verticalMode ? panel.hWidth : Math.round(64 * root.scaling)
radius: Style.radiusL * root.scaling
height: panel.verticalMode ? panel.vHeight : Math.round(64 * scaling)
radius: Style.radiusL * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: (function () {
const bw = Math.max(2, Math.round(Style.borderM * root.scaling))
const bw = Math.max(2, Math.round(Style.borderM * scaling))
return (bw % 2 === 0) ? bw : bw + 1
})()
visible: false
@@ -280,7 +295,7 @@ Variants {
NIcon {
icon: root.getIcon()
color: root.getIconColor()
pointSize: Style.fontSizeXL * root.scaling
pointSize: Style.fontSizeXL * scaling
Layout.alignment: Qt.AlignVCenter
Behavior on color {
@@ -326,7 +341,7 @@ Variants {
NText {
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS * root.scaling
pointSize: Style.fontSizeS * scaling
family: Settings.data.ui.fontFixed
Layout.alignment: Qt.AlignVCenter
horizontalAlignment: Text.AlignLeft
@@ -342,22 +357,18 @@ Variants {
ColumnLayout {
// Ensure inner padding respects the rounded corners; avoid clipping the icon/text
property int vMargin: (function () {
const styleMargin = Math.round(Style.marginL * root.scaling)
const styleMargin = Math.round(Style.marginL * scaling)
const cornerGuard = Math.round(osdItem.radius)
return Math.max(styleMargin, cornerGuard)
})()
property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * root.scaling))
property int balanceDelta: Math.round(Style.marginS * root.scaling)
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: vMarginTop
property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * scaling))
property int balanceDelta: Math.round(Style.marginS * scaling)
anchors.fill: parent
anchors.topMargin: vMargin
anchors.leftMargin: vMargin
anchors.rightMargin: vMargin
anchors.bottomMargin: vMargin
width: (function () {
const w = parent.width - (vMargin * 2)
return (w % 2 === 0) ? w : w - 1
})()
spacing: Math.round(Style.marginS * root.scaling)
spacing: Math.round(Style.marginS * scaling)
// Percentage text at top
Item {
@@ -367,7 +378,7 @@ Variants {
id: percentText
text: root.getDisplayPercentage()
color: Color.mOnSurface
pointSize: Style.fontSizeS * root.scaling
pointSize: Style.fontSizeS * scaling
family: Settings.data.ui.fontFixed
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
@@ -379,7 +390,7 @@ Variants {
// Progress bar
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillHeight: true // Fill remaining space between text and icon
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
@@ -416,9 +427,8 @@ Variants {
NIcon {
icon: root.getIcon()
color: root.getIconColor()
pointSize: Style.fontSizeXL * root.scaling
pointSize: Style.fontSizeXL * scaling
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.bottomMargin: vMargin + Math.round(Style.marginM * root.scaling) + balanceDelta
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
@@ -513,6 +523,8 @@ Variants {
muteInitialized = true
inputVolumeInitialized = true
inputMuteInitialized = true
// Don't initialize brightness here - let it initialize on first change like volume
connectBrightnessMonitors()
}
}
@@ -533,6 +545,7 @@ Variants {
}
function connectBrightnessMonitors() {
brightnessChangeCount = 0 // Reset change count when reconnecting
for (var i = 0; i < BrightnessService.monitors.length; i++) {
let monitor = BrightnessService.monitors[i]
// Disconnect first to avoid duplicate connections
@@ -542,7 +555,10 @@ Variants {
}
function onBrightnessChanged(newBrightness) {
if (!brightnessInitialized) {
brightnessChangeCount++
if (brightnessChangeCount <= BrightnessService.monitors.length) {
// This is likely the initial brightness value(s), don't show OSD
brightnessInitialized = true
} else {
showOSD("brightness")
+5 -6
View File
@@ -129,12 +129,11 @@ NPanel {
"label": "settings.bar.title",
"icon": "settings-bar",
"source": barTab
},
//{
// "id": SettingsPanel.Tab.ControlCenter,
// "label": "settings.control-center.title",
// "icon": "settings-bar",
// "source": controlCenterTab
}, //{
// "id": SettingsPanel.Tab.ControlCenter,
// "label": "settings.control-center.title",
// "icon": "settings-bar",
// "source": controlCenterTab
//},
{
"id": SettingsPanel.Tab.Dock,
+47 -39
View File
@@ -11,16 +11,16 @@ ColumnLayout {
// Cache for scheme JSON (can be flat or {dark, light})
property var schemeColorsCache: ({})
property int cacheVersion: 0 // Increment to trigger UI updates
spacing: Style.marginL * scaling
// Helper function to extract scheme name from path
function extractSchemeName(schemePath) {
var pathParts = schemePath.split("/")
var filename = pathParts[pathParts.length - 1] // Get filename
var schemeName = filename.replace(".json", "") // Remove .json extension
var filename = pathParts[pathParts.length - 1]
var schemeName = filename.replace(".json", "")
// Convert folder names back to display names
if (schemeName === "Noctalia-default") {
schemeName = "Noctalia (default)"
} else if (schemeName === "Noctalia-legacy") {
@@ -33,39 +33,52 @@ ColumnLayout {
}
// Helper function to get color from scheme file (supports dark/light variants)
function getSchemeColor(schemePath, colorKey) {
// Extract scheme name from path
var schemeName = extractSchemeName(schemePath)
function getSchemeColor(schemeName, colorKey) {
// Access cache version to create dependency
var _ = cacheVersion
// Try to get from cached data first
if (schemeColorsCache[schemeName]) {
var entry = schemeColorsCache[schemeName]
var variant = entry
// Check if scheme has dark/light variants
if (entry.dark || entry.light) {
variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark)
}
if (variant && variant[colorKey])
if (variant && variant[colorKey]) {
return variant[colorKey]
}
}
// Return a default color if not cached yet
return "#000000"
// Return visible defaults while loading
if (colorKey === "mSurface")
return Color.mSurfaceVariant
if (colorKey === "mPrimary")
return Color.mPrimary
if (colorKey === "mSecondary")
return Color.mSecondary
if (colorKey === "mTertiary")
return Color.mTertiary
if (colorKey === "mError")
return Color.mError
return Color.mOnSurfaceVariant
}
// This function is called by the FileView Repeater when a scheme file is loaded
function schemeLoaded(schemeName, jsonData) {
var value = jsonData || {}
var newCache = schemeColorsCache
newCache[schemeName] = value
schemeColorsCache = newCache
schemeColorsCache[schemeName] = value
// Force UI update by incrementing cache version
cacheVersion++
}
// When the list of available schemes changes, clear the cache.
// The Repeater below will automatically re-create the FileViews.
// When the list of available schemes changes, clear the cache
Connections {
target: ColorSchemeService
function onSchemesChanged() {
schemeColorsCache = {}
cacheVersion++
}
}
@@ -77,12 +90,10 @@ ColumnLayout {
onExited: function (exitCode) {
if (exitCode === 0) {
// Matugen exists, enable it
Settings.data.colorSchemes.useWallpaperColors = true
AppThemeService.generate()
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled"))
} else {
// Matugen not found
ToastService.showWarning(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed"))
}
}
@@ -91,7 +102,7 @@ ColumnLayout {
stderr: StdioCollector {}
}
// A non-visual Item to host the Repeater that loads the color scheme files.
// A non-visual Item to host the Repeater that loads the color scheme files
Item {
visible: false
id: fileLoaders
@@ -99,21 +110,19 @@ ColumnLayout {
Repeater {
model: ColorSchemeService.schemes
// The delegate is a Component, which correctly wraps the non-visual FileView
delegate: Item {
FileView {
path: modelData
blockLoading: true
blockLoading: false
onLoaded: {
// Extract scheme name from path
var schemeName = extractSchemeName(path)
var schemeName = root.extractSchemeName(path)
try {
var jsonData = JSON.parse(text())
root.schemeLoaded(schemeName, jsonData)
} catch (e) {
Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e)
root.schemeLoaded(schemeName, null) // Load defaults on parse error
root.schemeLoaded(schemeName, null)
}
}
}
@@ -127,13 +136,16 @@ ColumnLayout {
description: I18n.tr("settings.color-scheme.color-source.section.description")
}
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
// Dark Mode Toggle
NToggle {
label: I18n.tr("settings.color-scheme.color-source.dark-mode.label")
description: I18n.tr("settings.color-scheme.color-source.dark-mode.description")
checked: Settings.data.colorSchemes.darkMode
enabled: true
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
onToggled: checked => {
Settings.data.colorSchemes.darkMode = checked
root.cacheVersion++ // Force UI update for dark/light variants
}
}
// Use Wallpaper Colors
@@ -143,14 +155,12 @@ ColumnLayout {
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
// Check if matugen is installed
matugenCheck.running = true
} else {
Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled"))
if (Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
@@ -195,7 +205,6 @@ ColumnLayout {
onSelected: key => {
Settings.data.colorSchemes.matugenSchemeType = key
AppThemeService.generate()
}
}
@@ -232,15 +241,16 @@ ColumnLayout {
id: schemeItem
property string schemePath: modelData
property string schemeName: root.extractSchemeName(modelData)
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
height: 50 * scaling
radius: Style.radiusS * scaling
color: getSchemeColor(modelData, "mSurface")
color: root.getSchemeColor(schemeName, "mSurface")
border.width: Math.max(1, Style.borderL * scaling)
border.color: {
if (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(modelData)) {
if (Settings.data.colorSchemes.predefinedScheme === schemeName) {
return Color.mSecondary
}
if (itemMouseArea.containsMouse) {
@@ -255,12 +265,11 @@ ColumnLayout {
spacing: Style.marginXS * scaling
NText {
text: extractSchemeName(schemePath)
text: schemeItem.schemeName
pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
// Layout.maximumWidth: 150 * scaling
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
@@ -271,28 +280,28 @@ ColumnLayout {
width: 14 * scaling
height: 14 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mPrimary")
color: root.getSchemeColor(schemeItem.schemeName, "mPrimary")
}
Rectangle {
width: 14 * scaling
height: 14 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mSecondary")
color: root.getSchemeColor(schemeItem.schemeName, "mSecondary")
}
Rectangle {
width: 14 * scaling
height: 14 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
color: root.getSchemeColor(schemeItem.schemeName, "mTertiary")
}
Rectangle {
width: 14 * scaling
height: 14 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mError")
color: root.getSchemeColor(schemeItem.schemeName, "mError")
}
}
@@ -305,14 +314,14 @@ ColumnLayout {
Settings.data.colorSchemes.useWallpaperColors = false
Logger.log("ColorSchemeTab", "Disabled wallpaper colors")
Settings.data.colorSchemes.predefinedScheme = extractSchemeName(schemePath)
Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
}
// Selection indicator
Rectangle {
visible: (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(schemePath))
visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName)
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -3 * scaling
@@ -350,7 +359,6 @@ ColumnLayout {
checked: Settings.data.colorSchemes.generateTemplatesForPredefined
onToggled: checked => {
Settings.data.colorSchemes.generateTemplatesForPredefined = checked
// Re-generate templates if a predefined scheme is currently active
if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
+9 -5
View File
@@ -90,11 +90,15 @@ ColumnLayout {
NLabel {
label: modelData.name || "Unknown"
description: I18n.tr("system.monitor-description", {
"model": modelData.model,
"width": modelData.width,
"height": modelData.height
})
description: {
const compositorScale = CompositorService.getDisplayScale(modelData.name)
I18n.tr("system.monitor-description", {
"model": modelData.model,
"width": modelData.width * compositorScale,
"height": modelData.height * compositorScale,
"scale": compositorScale
})
}
}
// Scale
+17 -23
View File
@@ -29,30 +29,24 @@ ColumnLayout {
description: I18n.tr("settings.dock.appearance.section.description")
}
ColumnLayout {
spacing: Style.marginXXS * scaling
NComboBox {
Layout.fillWidth: true
NLabel {
label: I18n.tr("settings.dock.appearance.display.label")
description: I18n.tr("settings.dock.appearance.display.description")
}
NComboBox {
Layout.fillWidth: true
model: [{
"key": "always_visible",
"name": I18n.tr("settings.dock.appearance.display.always-visible")
}, {
"key": "auto_hide",
"name": I18n.tr("settings.dock.appearance.display.auto-hide")
}, {
"key": "exclusive",
"name": I18n.tr("settings.dock.appearance.display.exclusive")
}]
currentKey: Settings.data.dock.displayMode
onSelected: key => {
Settings.data.dock.displayMode = key
}
}
label: I18n.tr("settings.dock.appearance.display.label")
description: I18n.tr("settings.dock.appearance.display.description")
model: [{
"key": "always_visible",
"name": I18n.tr("settings.dock.appearance.display.always-visible")
}, {
"key": "auto_hide",
"name": I18n.tr("settings.dock.appearance.display.auto-hide")
}, {
"key": "exclusive",
"name": I18n.tr("settings.dock.appearance.display.exclusive")
}]
currentKey: Settings.data.dock.displayMode
onSelected: key => {
Settings.data.dock.displayMode = key
}
}
ColumnLayout {
+7
View File
@@ -83,6 +83,13 @@ ColumnLayout {
onToggled: checked => Settings.data.osd.enabled = checked
}
NToggle {
label: I18n.tr("settings.osd.always-on-top.label")
description: I18n.tr("settings.osd.always-on-top.description")
checked: Settings.data.osd.alwaysOnTop
onToggled: checked => Settings.data.osd.alwaysOnTop = checked
}
NLabel {
label: I18n.tr("settings.osd.duration.auto-hide.label")
description: I18n.tr("settings.osd.duration.auto-hide.description")