mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge branch 'main' into fix/calendar-alignment
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user