Settings: large cleanup and factorization. Should look much better.

This commit is contained in:
LemmyCook 2025-08-27 20:39:50 -04:00
parent 1206be34dc
commit 8302285388
21 changed files with 2434 additions and 2937 deletions

View file

@ -175,7 +175,7 @@ NPanel {
// Action buttons // Action buttons
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginS * scaling spacing: Style.marginL * scaling
NIconButton { NIconButton {
icon: "refresh" icon: "refresh"
@ -187,7 +187,6 @@ NPanel {
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
} }
NIconButton { NIconButton {
@ -201,7 +200,6 @@ NPanel {
colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary colorBg: ArchUpdaterService.updateInProgress ? Color.mSurfaceVariant : Color.mPrimary
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : Color.mOnPrimary
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
} }
NIconButton { NIconButton {
@ -219,7 +217,6 @@ NPanel {
colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount colorFg: ArchUpdaterService.updateInProgress ? Color.mOnSurfaceVariant : (ArchUpdaterService.selectedPackagesCount
> 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant) > 0 ? Color.mOnSecondary : Color.mOnSurfaceVariant)
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 35 * scaling
} }
} }
} }

View file

@ -267,7 +267,7 @@ NPanel {
// Tab label on the main right side // Tab label on the main right side
NText { NText {
text: root.tabsModel[currentTabIndex].label text: root.tabsModel[currentTabIndex].label
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mPrimary color: Color.mPrimary
Layout.fillWidth: true Layout.fillWidth: true
@ -287,21 +287,28 @@ NPanel {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
clip: true
Repeater { Repeater {
model: root.tabsModel model: root.tabsModel
onItemAdded: function (index, item) {
item.sourceComponent = root.tabsModel[index].source
}
delegate: Loader { delegate: Loader {
// All loaders will occupy the same space, stacked on top of each other.
anchors.fill: parent anchors.fill: parent
visible: index === root.currentTabIndex active: index === root.currentTabIndex
// The loader is only active (and uses memory) when its page is visible. sourceComponent: ColumnLayout {
active: visible ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
padding: Style.marginL * scaling
Loader {
active: true
sourceComponent: root.tabsModel[index].source
width: scrollView.availableWidth
}
}
}
} }
} }
} }

View file

@ -9,16 +9,12 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
property string latestVersion: GitHubService.latestVersion property string latestVersion: GitHubService.latestVersion
property string currentVersion: "Unknown" // Fallback version property string currentVersion: "Unknown" // Fallback version
property var contributors: GitHubService.contributors property var contributors: GitHubService.contributors
spacing: 0
Layout.fillWidth: true
Layout.fillHeight: true
Process { Process {
id: currentVersionProcess id: currentVersionProcess
@ -41,23 +37,9 @@ ColumnLayout {
} }
} }
ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
padding: Style.marginL * scaling
rightPadding: Style.marginM * scaling
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
NText { NText {
text: "Noctalia: quiet by design" text: "Noctalia Shell"
font.pointSize: Style.fontSizeXXXL * scaling font.pointSize: Style.fontSizeXXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface
@ -65,14 +47,6 @@ ColumnLayout {
Layout.bottomMargin: Style.marginS * scaling Layout.bottomMargin: Style.marginS * scaling
} }
NText {
text: "It may just be another quickshell setup but it won't get in your way."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurface
Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: Style.marginL * scaling
}
GridLayout { GridLayout {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
columns: 2 columns: 2
@ -163,8 +137,8 @@ ColumnLayout {
NDivider { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginL * scaling Layout.bottomMargin: Style.marginxL * scaling
} }
NText { NText {
@ -258,5 +232,4 @@ ColumnLayout {
} }
} }
} }
}
}

View file

@ -7,11 +7,8 @@ import qs.Commons
import qs.Services import qs.Services
ColumnLayout { ColumnLayout {
id: root
property real localVolume: AudioService.volume property real localVolume: AudioService.volume
// Connection used to open the pill when volume changes
Connections { Connections {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() { function onVolumeChanged() {
@ -19,463 +16,404 @@ ColumnLayout {
} }
} }
spacing: 0 // Master Volume
ColumnLayout {
ScrollView { spacing: Style.marginS * scaling
id: scrollView
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
padding: Style.marginM * scaling NLabel {
clip: true label: "Output Volume"
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff description: "System-wide volume level."
ScrollBar.vertical.policy: ScrollBar.AsNeeded }
RowLayout {
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily
// Probably because they have some quick fades in and out to avoid clipping
// We use a timer to space out the updates, to avoid lock up
Timer {
interval: Style.animationFast
running: true
repeat: true
onTriggered: {
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localVolume)
}
}
}
NSlider {
Layout.fillWidth: true
from: 0
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
value: localVolume
stepSize: 0.01
onMoved: {
localVolume = value
}
}
NText {
text: Math.floor(AudioService.volume * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
// Mute Toggle
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NToggle {
label: "Mute Audio Output"
description: "Mute or unmute the default audio output."
checked: AudioService.muted
onToggled: checked => {
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = checked
}
}
}
}
// Volume Step Size
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NSpinBox {
Layout.fillWidth: true
label: "Volume Step Size"
description: "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts)."
minimum: 1
maximum: 25
value: Settings.data.audio.volumeStep
stepSize: 1
suffix: "%"
onValueChanged: {
Settings.data.audio.volumeStep = value
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// AudioService Devices
ColumnLayout {
spacing: Style.marginS * scaling
NText {
text: "Audio Devices"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// -------------------------------
// Output Devices
ButtonGroup {
id: sinks
}
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginXS * scaling
spacing: 0 Layout.fillWidth: true
Layout.bottomMargin: Style.marginL * scaling
Item { NLabel {
Layout.fillWidth: true label: "Output Device"
Layout.preferredHeight: 0 description: "Select the desired audio output device."
} }
ColumnLayout { Repeater {
spacing: Style.marginXS * scaling model: AudioService.sinks
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: AudioService.sink?.id === modelData.id
onClicked: AudioService.setAudioSink(modelData)
text: modelData.description
}
}
}
// -------------------------------
// Input Devices
ButtonGroup {
id: sources
}
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL * scaling
NLabel {
label: "Input Device"
description: "Select the desired audio input device."
}
Repeater {
model: AudioService.sources
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: AudioService.source?.id === modelData.id
onClicked: AudioService.setAudioSource(modelData)
text: modelData.description
}
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Media Player Preferences
ColumnLayout {
spacing: Style.marginL * scaling
NText {
text: "Media Player"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// Miniplayer section
NToggle {
label: "Show Album Art In Bar Media Player"
description: "Show the album art of the currently playing song next to the title."
checked: Settings.data.audio.showMiniplayerAlbumArt
onToggled: checked => {
Settings.data.audio.showMiniplayerAlbumArt = checked
}
}
NToggle {
label: "Show Audio Visualizer In Bar Media Player"
description: "Shows an audio visualizer in the background of the miniplayer."
checked: Settings.data.audio.showMiniplayerCava
onToggled: checked => {
Settings.data.audio.showMiniplayerCava = checked
}
}
// Preferred player (persistent)
NTextInput {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
label: "Preferred Player"
description: "Substring to match MPRIS player (identity/bus/desktop)."
placeholderText: "e.g. spotify, vlc, mpv"
text: Settings.data.audio.preferredPlayer
onTextChanged: {
Settings.data.audio.preferredPlayer = text
MediaService.updateCurrentPlayer()
}
}
// Blacklist editor
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
RowLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NText { NTextInput {
text: "Audio Output Volume" id: blacklistInput
font.pointSize: Style.fontSizeXXL * scaling Layout.fillWidth: true
font.weight: Style.fontWeightBold Layout.alignment: Qt.AlignTop
color: Color.mOnSurface label: "Blacklist player"
Layout.bottomMargin: Style.marginS * scaling description: "Substring, e.g. plex, shim, mpv."
placeholderText: "type substring and press +"
} }
// Volume Controls // Button aligned to the center of the actual input field
ColumnLayout { NIconButton {
spacing: Style.marginS * scaling icon: "add"
Layout.fillWidth: true Layout.alignment: Qt.AlignBottom
Layout.topMargin: Style.marginS * scaling Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0
onClicked: {
// Master Volume const val = (blacklistInput.text || "").trim()
ColumnLayout { if (val !== "") {
spacing: Style.marginS * scaling const arr = (Settings.data.audio.mprisBlacklist || [])
Layout.fillWidth: true if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) {
Settings.data.audio.mprisBlacklist = [...arr, val]
NLabel { blacklistInput.text = ""
label: "Master Volume" MediaService.updateCurrentPlayer()
description: "System-wide volume level." }
} }
}
}
}
// Current blacklist entries
Flow {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS * scaling
spacing: Style.marginS * scaling
Repeater {
model: Settings.data.audio.mprisBlacklist
delegate: Rectangle {
required property string modelData
// Padding around the inner row
property real pad: Style.marginS * scaling
// Visuals
color: Color.applyOpacity(Color.mOnSurface, "20")
border.color: Color.applyOpacity(Color.mOnSurface, "50")
border.width: Math.max(1, Style.borderS * scaling)
// Content
RowLayout { RowLayout {
// Pipewire seems a bit finicky, if we spam too many volume changes it breaks easily id: chipRow
// Probably because they have some quick fades in and out to avoid clipping spacing: Style.marginXS * scaling
// We use a timer to space out the updates, to avoid lock up anchors.fill: parent
Timer { anchors.margins: pad
interval: Style.animationFast
running: true
repeat: true
onTriggered: {
if (Math.abs(localVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localVolume)
}
}
}
NSlider {
Layout.fillWidth: true
from: 0
to: Settings.data.audio.volumeOverdrive ? 2.0 : 1.0
value: localVolume
stepSize: 0.01
onMoved: {
localVolume = value
}
}
NText { NText {
text: Math.floor(AudioService.volume * 100) + "%" text: modelData
color: Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
} }
}
}
// Mute Toggle NIconButton {
ColumnLayout { icon: "close"
spacing: Style.marginS * scaling sizeRatio: 0.8
Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter
Layout.topMargin: Style.marginM * scaling Layout.rightMargin: Style.marginXS * scaling
onClicked: {
NToggle {
label: "Mute Audio Output"
description: "Mute or unmute the default audio output."
checked: AudioService.muted
onToggled: checked => {
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = checked
}
}
}
}
// Volume Step Size
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
NSpinBox {
Layout.fillWidth: true
label: "Volume Step Size"
description: "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts)."
minimum: 1
maximum: 25
value: Settings.data.audio.volumeStep
stepSize: 1
suffix: "%"
onValueChanged: {
Settings.data.audio.volumeStep = value
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// AudioService Devices
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Audio Devices"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// -------------------------------
// Output Devices
ButtonGroup {
id: sinks
}
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL * scaling
NLabel {
label: "Output Device"
description: "Select the desired audio output device."
}
Repeater {
model: AudioService.sinks
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: AudioService.sink?.id === modelData.id
onClicked: AudioService.setAudioSink(modelData)
text: modelData.description
}
}
}
}
// -------------------------------
// Input Devices
ButtonGroup {
id: sources
}
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL * scaling
NLabel {
label: "Input Device"
description: "Select the desired audio input device."
}
Repeater {
model: AudioService.sources
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: AudioService.source?.id === modelData.id
onClicked: AudioService.setAudioSource(modelData)
text: modelData.description
}
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// MPRIS Player Preferences
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "MPRIS Player Preferences"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Preferred player (persistent)
NTextInput {
label: "Preferred Player"
description: "Substring to match MPRIS player (identity/bus/desktop)."
placeholderText: "e.g. spotify, vlc, mpv"
text: Settings.data.audio.preferredPlayer
onTextChanged: {
Settings.data.audio.preferredPlayer = text
MediaService.updateCurrentPlayer()
}
}
// Blacklist editor
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
RowLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NTextInput {
id: blacklistInput
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
label: "Blacklist player"
description: "Substring, e.g. plex, shim, mpv"
placeholderText: "type substring and press +"
}
// Button aligned to the center of the actual input field
NIconButton {
icon: "add"
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0
onClicked: {
const val = (blacklistInput.text || "").trim()
if (val !== "") {
const arr = (Settings.data.audio.mprisBlacklist || []) const arr = (Settings.data.audio.mprisBlacklist || [])
if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) { const idx = arr.findIndex(x => String(x) === modelData)
Settings.data.audio.mprisBlacklist = [...arr, val] if (idx >= 0) {
blacklistInput.text = "" arr.splice(idx, 1)
Settings.data.audio.mprisBlacklist = arr
MediaService.updateCurrentPlayer() MediaService.updateCurrentPlayer()
} }
} }
} }
} }
// Intrinsic size derived from inner row + padding
implicitWidth: chipRow.implicitWidth + pad * 2
implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8 * scaling)
radius: Style.radiusM * scaling
} }
// Current blacklist entries
Flow {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS * scaling
spacing: Style.marginS * scaling
Repeater {
model: Settings.data.audio.mprisBlacklist
delegate: Rectangle {
required property string modelData
// Padding around the inner row
property real pad: Style.marginS * scaling
// Visuals
color: Color.applyOpacity(Color.mOnSurface, "20")
border.color: Color.applyOpacity(Color.mOnSurface, "50")
border.width: Math.max(1, Style.borderS * scaling)
// Content
RowLayout {
id: chipRow
spacing: Style.marginXS * scaling
anchors.fill: parent
anchors.margins: pad
NText {
text: modelData
color: Color.mOnSurface
font.pointSize: Style.fontSizeS * scaling
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
}
NIconButton {
icon: "close"
sizeRatio: 0.8
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Style.marginXS * scaling
onClicked: {
const arr = (Settings.data.audio.mprisBlacklist || [])
const idx = arr.findIndex(x => String(x) === modelData)
if (idx >= 0) {
arr.splice(idx, 1)
Settings.data.audio.mprisBlacklist = arr
MediaService.updateCurrentPlayer()
}
}
}
}
// Intrinsic size derived from inner row + padding
implicitWidth: chipRow.implicitWidth + pad * 2
implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8 * scaling)
radius: Style.radiusM * scaling
}
}
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Bar Mini Media player
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Bar Media Player"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Miniplayer section
NToggle {
label: "Show Album Art In Bar Media Player"
description: "Show the album art of the currently playing song next to the title."
checked: Settings.data.audio.showMiniplayerAlbumArt
onToggled: checked => {
Settings.data.audio.showMiniplayerAlbumArt = checked
}
}
NToggle {
label: "Show Audio Visualizer In Bar Media Player"
description: "Shows an audio visualizer in the background of the miniplayer."
checked: Settings.data.audio.showMiniplayerCava
onToggled: checked => {
Settings.data.audio.showMiniplayerCava = checked
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// AudioService Visualizer Category
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NText {
text: "Audio Visualizer"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// AudioService Visualizer section
NComboBox {
id: audioVisualizerCombo
label: "Visualization Type"
description: "Choose a visualization type for media playback"
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "linear"
name: "Linear"
}
ListElement {
key: "mirrored"
name: "Mirrored"
}
ListElement {
key: "wave"
name: "Wave"
}
}
currentKey: Settings.data.audio.visualizerType
onSelected: key => {
Settings.data.audio.visualizerType = key
}
}
NComboBox {
label: "Frame Rate"
description: "Target frame rate for audio visualizer. (default: 60)"
model: ListModel {
ListElement {
key: "30"
name: "30 FPS"
}
ListElement {
key: "60"
name: "60 FPS"
}
ListElement {
key: "100"
name: "100 FPS"
}
ListElement {
key: "120"
name: "120 FPS"
}
ListElement {
key: "144"
name: "144 FPS"
}
ListElement {
key: "165"
name: "165 FPS"
}
ListElement {
key: "240"
name: "240 FPS"
}
}
currentKey: Settings.data.audio.cavaFrameRate
onSelected: key => {
Settings.data.audio.cavaFrameRate = key
}
} }
} }
} }
} }
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// AudioService Visualizer Category
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NText {
text: "Audio Visualizer"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// AudioService Visualizer section
NComboBox {
id: audioVisualizerCombo
label: "Visualization Type"
description: "Choose a visualization type for media playback"
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "linear"
name: "Linear"
}
ListElement {
key: "mirrored"
name: "Mirrored"
}
ListElement {
key: "wave"
name: "Wave"
}
}
currentKey: Settings.data.audio.visualizerType
onSelected: key => {
Settings.data.audio.visualizerType = key
}
}
NComboBox {
label: "Frame Rate"
description: "Target frame rate for audio visualizer."
model: ListModel {
ListElement {
key: "30"
name: "30 FPS"
}
ListElement {
key: "60"
name: "60 FPS"
}
ListElement {
key: "100"
name: "100 FPS"
}
ListElement {
key: "120"
name: "120 FPS"
}
ListElement {
key: "144"
name: "144 FPS"
}
ListElement {
key: "165"
name: "165 FPS"
}
ListElement {
key: "240"
name: "240 FPS"
}
}
currentKey: Settings.data.audio.cavaFrameRate
onSelected: key => {
Settings.data.audio.cavaFrameRate = key
}
}
}
// Divider
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -6,221 +6,181 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root
spacing: 0 ColumnLayout {
spacing: Style.marginL * scaling
ScrollView { RowLayout {
id: scrollView NComboBox {
Layout.fillWidth: true
Layout.fillWidth: true label: "Bar Position"
Layout.fillHeight: true description: "Choose where to place the bar on the screen."
padding: Style.marginM * scaling model: ListModel {
clip: true ListElement {
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff key: "top"
ScrollBar.vertical.policy: ScrollBar.AsNeeded name: "Top"
}
ListElement {
key: "bottom"
name: "Bottom"
}
}
currentKey: Settings.data.bar.position
onSelected: key => Settings.data.bar.position = key
}
}
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginXXS * scaling
spacing: 0 Layout.fillWidth: true
Item { NText {
Layout.fillWidth: true text: "Background Opacity"
Layout.preferredHeight: 0 font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
} }
ColumnLayout { NText {
spacing: Style.marginL * scaling text: "Adjust the background opacity of the bar"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
}
ColumnLayout { RowLayout {
spacing: Style.marginXXS * scaling NSlider {
Layout.fillWidth: true Layout.fillWidth: true
from: 0
NText { to: 1
text: "Bar Position" stepSize: 0.01
font.pointSize: Style.fontSizeL * scaling value: Settings.data.bar.backgroundOpacity
font.weight: Style.fontWeightBold onMoved: Settings.data.bar.backgroundOpacity = value
color: Color.mOnSurface cutoutColor: Color.mSurface
}
NText {
text: "Choose where to place the bar on the screen"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
NComboBox {
Layout.fillWidth: true
model: ListModel {
ListElement {
key: "top"
name: "Top"
}
ListElement {
key: "bottom"
name: "Bottom"
}
}
currentKey: Settings.data.bar.position
onSelected: key => {
Settings.data.bar.position = key
}
}
} }
ColumnLayout { NText {
spacing: Style.marginXXS * scaling text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
NText { color: Color.mOnSurface
text: "Background Opacity"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the background opacity of the bar"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.bar.backgroundOpacity
onMoved: Settings.data.bar.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.bar.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
NToggle {
label: "Show Active Window's Icon"
description: "Display the app icon next to the title of the currently focused window."
checked: Settings.data.bar.showActiveWindowIcon
onToggled: checked => {
Settings.data.bar.showActiveWindowIcon = checked
}
}
NToggle {
label: "Show Battery Percentage"
description: "Display battery percentage at all times."
checked: Settings.data.bar.alwaysShowBatteryPercentage
onToggled: checked => {
Settings.data.bar.alwaysShowBatteryPercentage = checked
}
}
NComboBox {
label: "Show Workspaces Labels"
description: "Display the workspace name or index in the workspace indicator"
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "index"
name: "Index"
}
ListElement {
key: "name"
name: "Name"
}
}
currentKey: Settings.data.bar.showWorkspaceLabel
onSelected: key => {
Settings.data.bar.showWorkspaceLabel = key
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Widgets Positioning"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NText {
text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
// Bar Sections
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
// Left Section
NSectionEditor {
sectionName: "Left"
widgetModel: Settings.data.bar.widgets.left
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Center Section
NSectionEditor {
sectionName: "Center"
widgetModel: Settings.data.bar.widgets.center
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Right Section
NSectionEditor {
sectionName: "Right"
widgetModel: Settings.data.bar.widgets.right
availableWidgets: availableWidgets
scrollView: scrollView
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
}
} }
} }
} }
NToggle {
label: "Show Active Window's Icon"
description: "Display the app icon next to the title of the currently focused window."
checked: Settings.data.bar.showActiveWindowIcon
onToggled: checked => {
Settings.data.bar.showActiveWindowIcon = checked
}
}
NToggle {
label: "Show Battery Percentage"
description: "Display battery percentage at all times."
checked: Settings.data.bar.alwaysShowBatteryPercentage
onToggled: checked => {
Settings.data.bar.alwaysShowBatteryPercentage = checked
}
}
NComboBox {
label: "Show Workspaces Labels"
description: "Display the workspace name or index in the workspace indicator"
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "index"
name: "Index"
}
ListElement {
key: "name"
name: "Name"
}
}
currentKey: Settings.data.bar.showWorkspaceLabel
onSelected: key => {
Settings.data.bar.showWorkspaceLabel = key
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Widgets Positioning"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NText {
text: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
// Bar Sections
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
// Left Section
NSectionEditor {
sectionName: "Left"
widgetModel: Settings.data.bar.widgets.left
availableWidgets: availableWidgets
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Center Section
NSectionEditor {
sectionName: "Center"
widgetModel: Settings.data.bar.widgets.center
availableWidgets: availableWidgets
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
// Right Section
NSectionEditor {
sectionName: "Right"
widgetModel: Settings.data.bar.widgets.right
availableWidgets: availableWidgets
onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section)
onRemoveWidget: (section, index) => removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex)
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
} }
// Helper functions // Helper functions

View file

@ -6,151 +6,121 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { ColumnLayout {
readonly property real scaling: ScalingService.scale(screen) readonly property real scaling: ScalingService.scale(screen)
readonly property string tabIcon: "brightness_6" readonly property string tabIcon: "brightness_6"
readonly property string tabLabel: "Brightness" readonly property string tabLabel: "Brightness"
Layout.fillWidth: true spacing: Style.marginL * scaling
Layout.fillHeight: true
ScrollView { // Brightness Step Section
anchors.fill: parent ColumnLayout {
clip: true spacing: Style.marginS * scaling
ScrollBar.vertical.policy: ScrollBar.AsNeeded Layout.fillWidth: true
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
contentWidth: parent.width
ColumnLayout { NSpinBox {
width: parent.width Layout.fillWidth: true
ColumnLayout { label: "Brightness Step Size"
spacing: Style.marginL * scaling description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)."
Layout.margins: Style.marginL * scaling minimum: 1
maximum: 50
value: Settings.data.brightness.brightnessStep
stepSize: 1
suffix: "%"
onValueChanged: {
Settings.data.brightness.brightnessStep = value
}
}
}
// Monitor Overview Section
ColumnLayout {
spacing: Style.marginL * scaling
NLabel {
label: "Monitors Brightness Control"
description: "Current brightness levels for all detected monitors."
}
// Single monitor display using the same data source as the bar icon
Repeater {
model: BrightnessService.monitors
Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
NText {
text: "Brightness Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
// Brightness Step Section
ColumnLayout { ColumnLayout {
spacing: Style.marginS * scaling id: contentCol
Layout.fillWidth: true anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
NSpinBox { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
label: "Brightness Step Size" spacing: Style.marginM * scaling
description: "Adjust the step size for brightness changes (scroll wheel, keyboard shortcuts)."
minimum: 1 NText {
maximum: 50 text: `${model.modelData.name} [${model.modelData.model}]`
value: Settings.data.brightness.brightnessStep font.pointSize: Style.fontSizeL * scaling
stepSize: 1 font.weight: Style.fontWeightBold
suffix: "%" color: Color.mSecondary
onValueChanged: { }
Settings.data.brightness.brightnessStep = value
Item {
Layout.fillWidth: true
}
NText {
text: model.method
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignRight
} }
} }
}
NDivider { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling spacing: Style.marginM * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Monitor Overview Section NText {
ColumnLayout { text: "Brightness:"
spacing: Style.marginS * scaling font.pointSize: Style.fontSizeM * scaling
Layout.fillWidth: true color: Color.mOnSurface
}
NLabel { NSlider {
label: "Monitors Brightness Control"
description: "Current brightness levels for all detected monitors."
}
// Single monitor display using the same data source as the bar icon
Repeater {
model: BrightnessService.monitors
Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
radius: Style.radiusM * scaling from: 0
color: Color.mSurface to: 1
border.color: Color.mOutline value: model.brightness
border.width: Math.max(1, Style.borderS * scaling) stepSize: 0.05
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling onPressedChanged: {
if (!pressed) {
ColumnLayout { var monitor = BrightnessService.getMonitorForScreen(model.modelData)
id: contentCol monitor.setBrightness(value)
anchors.fill: parent
anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: `${model.modelData.name} [${model.modelData.model}]`
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
Item {
Layout.fillWidth: true
}
NText {
text: model.method
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignRight
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM * scaling
NText {
text: "Brightness:"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurface
}
NSlider {
Layout.fillWidth: true
from: 0
to: 1
value: model.brightness
stepSize: 0.05
onPressedChanged: {
if (!pressed) {
var monitor = BrightnessService.getMonitorForScreen(model.modelData)
monitor.setBrightness(value)
}
}
}
NText {
text: Math.round(model.brightness * 100) + "%"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignRight
}
} }
} }
} }
}
}
Item { NText {
Layout.fillHeight: true text: Math.round(model.brightness * 100) + "%"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.alignment: Qt.AlignRight
}
}
} }
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -9,8 +9,6 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: 0
// Helper function to get color from scheme file (supports dark/light variants) // Helper function to get color from scheme file (supports dark/light variants)
function getSchemeColor(schemePath, colorKey) { function getSchemeColor(schemePath, colorKey) {
// Extract scheme name from path // Extract scheme name from path
@ -55,6 +53,28 @@ ColumnLayout {
} }
} }
// Simple process to check if matugen exists
Process {
id: matugenCheck
command: ["which", "matugen"]
running: false
onExited: function (exitCode) {
if (exitCode === 0) {
// Matugen exists, enable it
Settings.data.colorSchemes.useWallpaperColors = true
ColorSchemeService.changedWallpaper()
ToastService.showNotice("Matugen", "Enabled!")
} else {
// Matugen not found
ToastService.showWarning("Matugen", "Not installed!")
}
}
stdout: StdioCollector {}
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 { Item {
visible: false visible: false
@ -83,285 +103,250 @@ ColumnLayout {
} }
} }
// UI Code ColumnLayout {
ScrollView { width: scrollView.availableWidth
id: scrollView spacing: 0
Layout.fillWidth: true Item {
Layout.fillHeight: true Layout.fillWidth: true
padding: Style.marginM * scaling Layout.preferredHeight: 0
clip: true }
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginL * scaling
spacing: 0 Layout.fillWidth: true
Item { // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
NToggle {
label: "Dark Mode"
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
checked: Settings.data.colorSchemes.darkMode
enabled: true
onToggled: checked => {
Settings.data.colorSchemes.darkMode = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
} else if (Settings.data.colorSchemes.predefinedScheme) {
// Re-apply current scheme to pick the right variant
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
// Force refresh of previews
var tmp = schemeColorsCache
schemeColorsCache = {}
schemeColorsCache = tmp
}
}
}
// App theming
NToggle {
label: "Theme external apps (GTK, Qt & kitty)"
description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors."
checked: Settings.data.colorSchemes.themeApps
onToggled: checked => {
Settings.data.colorSchemes.themeApps = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
}
}
}
// Use Matugen
NToggle {
label: "Enable Matugen"
description: "Automatically generate colors based on your active wallpaper."
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("Matugen", "Disabled")
}
}
}
NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 0 Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
} }
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) NText {
NToggle { text: "Predefined Color Schemes"
label: "Dark Mode" font.pointSize: Style.fontSizeXXL * scaling
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." font.weight: Style.fontWeightBold
checked: Settings.data.colorSchemes.darkMode color: Color.mSecondary
enabled: true
onToggled: checked => {
Settings.data.colorSchemes.darkMode = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
} else if (Settings.data.colorSchemes.predefinedScheme) {
// Re-apply current scheme to pick the right variant
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
// Force refresh of previews
var tmp = schemeColorsCache
schemeColorsCache = {}
schemeColorsCache = tmp
}
}
} }
// App theming NText {
NToggle { text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen."
label: "Theme external apps (GTK, Qt & kitty)" font.pointSize: Style.fontSizeM * scaling
description: "Writes GTK (gtk.css), Qt5/6 (noctalia.conf) and Kitty (noctalia.conf) themes based on your colors." color: Color.mOnSurfaceVariant
checked: Settings.data.colorSchemes.themeApps
onToggled: checked => {
Settings.data.colorSchemes.themeApps = checked
if (Settings.data.colorSchemes.useWallpaperColors) {
ColorSchemeService.changedWallpaper()
}
}
}
// Use Matugen
NToggle {
label: "Enable Matugen"
description: "Automatically generate colors based on your active wallpaper."
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("Matugen", "Disabled")
}
}
}
NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling wrapMode: Text.WordWrap
Layout.bottomMargin: Style.marginL * scaling
} }
}
ColumnLayout { // Color Schemes Grid
spacing: Style.marginXXS * scaling GridLayout {
Layout.fillWidth: true columns: 4
rowSpacing: Style.marginL * scaling
columnSpacing: Style.marginL * scaling
Layout.fillWidth: true
Repeater {
model: ColorSchemeService.schemes
Rectangle {
id: schemeCard
property string schemePath: modelData
NText {
text: "Predefined Color Schemes"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true Layout.fillWidth: true
} Layout.preferredHeight: 120 * scaling
radius: Style.radiusM * scaling
color: getSchemeColor(modelData, "mSurface")
border.width: Math.max(1, Style.borderL * scaling)
border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow
NText { // Mouse area for selection
text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead. You can toggle between light and dark themes when using Matugen." MouseArea {
font.pointSize: Style.fontSizeXS * scaling anchors.fill: parent
color: Color.mOnSurface onClicked: {
Layout.fillWidth: true // Disable useWallpaperColors when picking a predefined color scheme
wrapMode: Text.WordWrap Settings.data.colorSchemes.useWallpaperColors = false
} Logger.log("ColorSchemeTab", "Disabled matugen setting")
}
ColumnLayout { Settings.data.colorSchemes.predefinedScheme = schemePath
spacing: Style.marginXS * scaling ColorSchemeService.applyScheme(schemePath)
Layout.fillWidth: true }
Layout.topMargin: Style.marginL * scaling hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// Color Schemes Grid onEntered: {
GridLayout { schemeCard.scale = root.cardScaleHigh
columns: 4 }
rowSpacing: Style.marginL * scaling
columnSpacing: Style.marginL * scaling
Layout.fillWidth: true
Repeater { onExited: {
model: ColorSchemeService.schemes schemeCard.scale = root.cardScaleLow
}
}
Rectangle { // Card content
id: schemeCard ColumnLayout {
anchors.fill: parent
property string schemePath: modelData anchors.margins: Style.marginXL * scaling
spacing: Style.marginS * scaling
// Scheme name
NText {
text: {
// Remove json and the full path
var chunks = schemePath.replace(".json", "").split("/")
return chunks[chunks.length - 1]
}
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: getSchemeColor(modelData, "mOnSurface")
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 120 * scaling elide: Text.ElideRight
radius: Style.radiusM * scaling horizontalAlignment: Text.AlignHCenter
color: getSchemeColor(modelData, "mSurface") }
border.width: Math.max(1, Style.borderL * scaling)
border.color: Settings.data.colorSchemes.predefinedScheme === modelData ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow
// Mouse area for selection // Color swatches
MouseArea { RowLayout {
anchors.fill: parent spacing: Style.marginS * scaling
onClicked: { Layout.fillWidth: true
// Disable useWallpaperColors when picking a predefined color scheme Layout.alignment: Qt.AlignHCenter
Settings.data.colorSchemes.useWallpaperColors = false
Logger.log("ColorSchemeTab", "Disabled matugen setting")
Settings.data.colorSchemes.predefinedScheme = schemePath // Primary color swatch
ColorSchemeService.applyScheme(schemePath)
}
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
schemeCard.scale = root.cardScaleHigh
}
onExited: {
schemeCard.scale = root.cardScaleLow
}
}
// Card content
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginXL * scaling
spacing: Style.marginS * scaling
// Scheme name
NText {
text: {
// Remove json and the full path
var chunks = schemePath.replace(".json", "").split("/")
return chunks[chunks.length - 1]
}
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: getSchemeColor(modelData, "mOnSurface")
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
// Color swatches
RowLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
// Primary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mPrimary")
}
// Secondary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mSecondary")
}
// Tertiary color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
}
// Error color swatch
Rectangle {
width: 28 * scaling
height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mError")
}
}
}
// Selection indicator
Rectangle { Rectangle {
visible: Settings.data.colorSchemes.predefinedScheme === schemePath width: 28 * scaling
anchors.right: parent.right height: 28 * scaling
anchors.top: parent.top
anchors.margins: Style.marginS * scaling
width: 24 * scaling
height: 24 * scaling
radius: width * 0.5 radius: width * 0.5
color: Color.mPrimary color: getSchemeColor(modelData, "mPrimary")
NText {
anchors.centerIn: parent
text: "✓"
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
} }
// Smooth animations // Secondary color swatch
Behavior on scale { Rectangle {
NumberAnimation { width: 28 * scaling
duration: Style.animationNormal height: 28 * scaling
easing.type: Easing.OutCubic radius: width * 0.5
} color: getSchemeColor(modelData, "mSecondary")
} }
Behavior on border.color { // Tertiary color swatch
ColorAnimation { Rectangle {
duration: Style.animationNormal width: 28 * scaling
} height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
} }
Behavior on border.width { // Error color swatch
NumberAnimation { Rectangle {
duration: Style.animationFast width: 28 * scaling
} height: 28 * scaling
radius: width * 0.5
color: getSchemeColor(modelData, "mError")
} }
} }
} }
// Selection indicator
Rectangle {
visible: Settings.data.colorSchemes.predefinedScheme === schemePath
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Style.marginS * scaling
width: 24 * scaling
height: 24 * scaling
radius: width * 0.5
color: Color.mPrimary
NText {
anchors.centerIn: parent
text: "✓"
font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
}
// Smooth animations
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationNormal
}
}
Behavior on border.width {
NumberAnimation {
duration: Style.animationFast
}
}
} }
} }
} }
} }
} }
// Simple process to check if matugen exists NDivider {
Process { Layout.fillWidth: true
id: matugenCheck Layout.topMargin: Style.marginXL * scaling
command: ["which", "matugen"] Layout.bottomMargin: Style.marginXL * scaling
running: false
onExited: function (exitCode) {
if (exitCode === 0) {
// Matugen exists, enable it
Settings.data.colorSchemes.useWallpaperColors = true
ColorSchemeService.changedWallpaper()
ToastService.showNotice("Matugen", "Enabled!")
} else {
// Matugen not found
ToastService.showWarning("Matugen", "Not installed!")
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
} }
} }

View file

@ -6,13 +6,11 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { ColumnLayout {
readonly property real scaling: ScalingService.scale(screen) readonly property real scaling: ScalingService.scale(screen)
readonly property string tabIcon: "monitor" readonly property string tabIcon: "monitor"
readonly property string tabLabel: "Display" readonly property string tabLabel: "Display"
readonly property int tabIndex: 5 readonly property int tabIndex: 5
Layout.fillWidth: true
Layout.fillHeight: true
// Time dropdown options (00:00 .. 23:30) // Time dropdown options (00:00 .. 23:30)
ListModel { ListModel {
@ -45,181 +43,167 @@ Item {
}) })
} }
ScrollView {
anchors.fill: parent NText {
clip: true text: "Monitor-specific configuration"
ScrollBar.vertical.policy: ScrollBar.AsNeeded font.pointSize: Style.fontSizeL * scaling
ScrollBar.horizontal.policy: ScrollBar.AsNeeded font.weight: Style.fontWeightBold
contentWidth: Math.max(parent.width, 600 * scaling) }
NText {
text: "Bars and notifications appear on all displays by default. Choose specific displays below to limit where they're shown."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling)
}
ColumnLayout { ColumnLayout {
id: contentColumn spacing: Style.marginL * scaling
width: Math.max(parent.width, 600 * scaling) Layout.topMargin: Style.marginL * scaling
ColumnLayout { Repeater {
spacing: Style.marginL * scaling model: Quickshell.screens || []
Layout.margins: Style.marginL * scaling delegate: Rectangle {
Layout.fillWidth: true
NText {
text: "Permonitor configuration"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "By default, bars and notifications are shown on all displays. Select one or more below to narrow your view."
font.pointSize: Style.fontSize * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) Layout.minimumWidth: 550 * scaling
} radius: Style.radiusM * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling
Repeater { ColumnLayout {
model: Quickshell.screens || [] id: contentCol
delegate: Rectangle { anchors.fill: parent
Layout.fillWidth: true anchors.margins: Style.marginL * scaling
Layout.minimumWidth: 550 * scaling spacing: Style.marginXXS * scaling
radius: Style.radiusM * scaling
color: Color.mSurface NText {
border.color: Color.mOutline text: (modelData.name || "Unknown")
border.width: Math.max(1, Style.borderS * scaling) font.pointSize: Style.fontSizeXL * scaling
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NText {
text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})`
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
ColumnLayout { ColumnLayout {
id: contentCol spacing: Style.marginL * scaling
anchors.fill: parent Layout.fillWidth: true
anchors.margins: Style.marginL * scaling
spacing: Style.marginXXS * scaling
NText { NToggle {
text: (modelData.name || "Unknown") Layout.fillWidth: true
font.pointSize: Style.fontSizeXL * scaling label: "Bar"
font.weight: Style.fontWeightBold description: "Enable the bar on this monitor."
color: Color.mSecondary checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
} }
NText { NToggle {
text: `Resolution: ${modelData.width}x${modelData.height} - Position: (${modelData.x}, ${modelData.y})`
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
label: "Notifications"
description: "Enable notifications on this monitor."
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors,
modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(
Settings.data.notifications.monitors, modelData.name)
}
}
}
NToggle {
Layout.fillWidth: true
label: "Dock"
description: "Enable the dock on this monitor."
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
} else {
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors, modelData.name)
}
}
} }
ColumnLayout { ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NToggle { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
label: "Bar" spacing: Style.marginL * scaling
description: "Enable the bar on this monitor."
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
} else {
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
}
}
}
NToggle { ColumnLayout {
Layout.fillWidth: true spacing: Style.marginXXS * scaling
label: "Notifications"
description: "Enable notifications on this monitor."
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.notifications.monitors = addMonitor(
Settings.data.notifications.monitors, modelData.name)
} else {
Settings.data.notifications.monitors = removeMonitor(
Settings.data.notifications.monitors, modelData.name)
}
}
}
NToggle {
Layout.fillWidth: true
label: "Dock"
description: "Enable the dock on this monitor."
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
Settings.data.dock.monitors = addMonitor(Settings.data.dock.monitors, modelData.name)
} else {
Settings.data.dock.monitors = removeMonitor(Settings.data.dock.monitors,
modelData.name)
}
}
}
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Style.marginL * scaling
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Scale"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Scale the user interface on this monitor."
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NText { NText {
text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%` text: "Scale"
Layout.alignment: Qt.AlignVCenter font.pointSize: Style.fontSizeM * scaling
Layout.minimumWidth: 50 * scaling font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignRight color: Color.mOnSurface
}
NText {
text: "Scale the user interface on this monitor."
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
} }
} }
RowLayout { NText {
spacing: Style.marginS * scaling text: `${Math.round(ScalingService.scaleByName(modelData.name) * 100)}%`
Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: 50 * scaling
horizontalAlignment: Text.AlignRight
}
}
NSlider { RowLayout {
id: scaleSlider spacing: Style.marginS * scaling
from: 0.7 Layout.fillWidth: true
to: 1.8
stepSize: 0.01 NSlider {
value: ScalingService.scaleByName(modelData.name) id: scaleSlider
onPressedChanged: { from: 0.7
var data = Settings.data.monitorsScaling || {} to: 1.8
data[modelData.name] = value stepSize: 0.01
Settings.data.monitorsScaling = data value: ScalingService.scaleByName(modelData.name)
} onPressedChanged: {
Layout.fillWidth: true var data = Settings.data.monitorsScaling || {}
Layout.minimumWidth: 150 * scaling data[modelData.name] = value
Settings.data.monitorsScaling = data
} }
Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling
}
NIconButton { NIconButton {
icon: "refresh" icon: "refresh"
tooltipText: "Reset Scaling" tooltipText: "Reset Scaling"
onClicked: { onClicked: {
var data = Settings.data.monitorsScaling || {} var data = Settings.data.monitorsScaling || {}
data[modelData.name] = 1.0 data[modelData.name] = 1.0
Settings.data.monitorsScaling = data Settings.data.monitorsScaling = data
}
} }
} }
} }
@ -227,68 +211,56 @@ Item {
} }
} }
} }
}
// Night Light Section NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Night Light Section
ColumnLayout {
spacing: Style.marginXS * scaling
NText { NText {
text: "Night Light" text: "Night Light"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mSecondary
Layout.topMargin: Style.marginXL * scaling
} }
NText { NText {
text: "Reduce blue light emission to help you sleep better and reduce eye strain." text: "Reduce blue light emission to help you sleep better and reduce eye strain."
font.pointSize: Style.fontSize * scaling font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling) Layout.preferredWidth: parent.width - (Style.marginL * 2 * scaling)
} }
}
NToggle { NToggle {
label: "Enable Night Light" label: "Enable Night Light"
description: "Apply a warm color filter to reduce blue light emission." description: "Apply a warm color filter to reduce blue light emission."
checked: Settings.data.nightLight.enabled checked: Settings.data.nightLight.enabled
onToggled: checked => { onToggled: checked => Settings.data.nightLight.enabled = checked
Settings.data.nightLight.enabled = checked }
}
NToggle {
label: "Auto Schedule"
description: "Automatically enable night light based on time schedule."
checked: Settings.data.nightLight.autoSchedule
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
}
// Intensity settings
ColumnLayout {
NLabel {
label: "Intensity"
description: "Higher values create warmer light."
} }
NToggle {
label: "Auto Schedule"
description: "Automatically enable night light based on time schedule."
checked: Settings.data.nightLight.autoSchedule
enabled: Settings.data.nightLight.enabled
onToggled: checked => Settings.data.nightLight.autoSchedule = checked
}
// Intensity settings
ColumnLayout {
spacing: Style.marginXS * scaling
NText {
text: "Intensity"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
enabled: Settings.data.nightLight.enabled
}
NText {
text: "Higher values create warmer (more orange) light, lower values create cooler (more blue) light."
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
enabled: Settings.data.nightLight.enabled
}
}
RowLayout { RowLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true
enabled: Settings.data.nightLight.enabled
NSlider { NSlider {
from: 0 from: 0
@ -307,66 +279,58 @@ Item {
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
} }
} }
}
// Schedule settings // Schedule settings
ColumnLayout { ColumnLayout {
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
NText { NLabel {
text: "Schedule" label: "Schedule"
font.pointSize: Style.fontSizeM * scaling description: "Set a start and end time for automatic schedule."
font.weight: Style.fontWeightBold
color: Color.mOnSurface
enabled: Settings.data.nightLight.enabled
}
RowLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
enabled: Settings.data.nightLight.enabled
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Start Time"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.startTime
placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.startTime = key
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Stop Time"
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.stopTime
placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.stopTime = key
}
}
}
} }
Item { RowLayout {
Layout.fillHeight: true Layout.fillWidth: false
spacing: Style.marginM * scaling
NText {
text: "Start Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.startTime
placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.startTime = key
preferredWidth: 120 * scaling
}
Item {// add a little more spacing
}
NText {
text: "Stop Time"
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
}
NComboBox {
model: timeOptions
currentKey: Settings.data.nightLight.stopTime
placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.stopTime = key
preferredWidth: 120 * scaling
}
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
} }
} }

View file

@ -6,251 +6,201 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root
spacing: 0
ScrollView {
id: scrollView
// Profile section
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true spacing: Style.marginL * scaling
padding: Style.marginM * scaling
clip: true // Avatar preview
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff NImageCircled {
ScrollBar.vertical.policy: ScrollBar.AsNeeded width: 64 * scaling
height: 64 * scaling
imagePath: Settings.data.general.avatarImage
fallbackIcon: "person"
borderColor: Color.mPrimary
borderWidth: Math.max(1, Style.borderM * scaling)
}
NTextInput {
label: "Profile Picture"
description: "Your profile picture displayed in various places throughout the shell."
text: Settings.data.general.avatarImage
placeholderText: "/home/user/.face"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.general.avatarImage = text
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// User Interface
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "User Interface"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Show Corners"
description: "Display rounded corners on the edge of the screen."
checked: Settings.data.general.showScreenCorners
onToggled: checked => {
Settings.data.general.showScreenCorners = checked
}
}
NToggle {
label: "Dim Desktop"
description: "Dim the desktop when panels or menus are open."
checked: Settings.data.general.dimDesktop
onToggled: checked => {
Settings.data.general.dimDesktop = checked
}
}
NToggle {
label: "Auto-hide Dock"
description: "Automatically hide the dock when not in use."
checked: Settings.data.dock.autoHide
onToggled: checked => {
Settings.data.dock.autoHide = checked
}
}
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginXXS * scaling
spacing: 0 Layout.fillWidth: true
Item { NLabel {
Layout.fillWidth: true label: "Border radius"
Layout.preferredHeight: 0 description: "Adjust the rounded border of all UI elements."
} }
ColumnLayout { RowLayout {
spacing: Style.marginL * scaling NSlider {
Layout.fillWidth: true
NText {
text: "General Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
// Profile section
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling from: 0
to: 1
RowLayout { stepSize: 0.01
Layout.fillWidth: true value: Settings.data.general.radiusRatio
spacing: Style.marginL * scaling onMoved: Settings.data.general.radiusRatio = value
cutoutColor: Color.mSurface
// Avatar preview
NImageCircled {
width: 64 * scaling
height: 64 * scaling
imagePath: Settings.data.general.avatarImage
fallbackIcon: "person"
borderColor: Color.mPrimary
borderWidth: Math.max(1, Style.borderM * scaling)
}
NTextInput {
label: "Profile Picture"
description: "Your profile picture displayed in various places throughout the shell."
text: Settings.data.general.avatarImage
placeholderText: "/home/user/.face"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.general.avatarImage = text
}
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "User Interface"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Show Corners"
description: "Display rounded corners on the edge of the screen."
checked: Settings.data.general.showScreenCorners
onToggled: checked => {
Settings.data.general.showScreenCorners = checked
}
}
NToggle {
label: "Dim Desktop"
description: "Dim the desktop when panels or menus are open."
checked: Settings.data.general.dimDesktop
onToggled: checked => {
Settings.data.general.dimDesktop = checked
}
}
NToggle {
label: "Auto-hide Dock"
description: "Automatically hide the dock when not in use."
checked: Settings.data.dock.autoHide
onToggled: checked => {
Settings.data.dock.autoHide = checked
}
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Border radius"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the rounded border of all UI elements"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.general.radiusRatio
onMoved: Settings.data.general.radiusRatio = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Animation Speed
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Animation Speed"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust global animation speed (0.1× to 2.0×)"
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0.1
to: 2.0
stepSize: 0.01
value: Settings.data.general.animationSpeed
onMoved: Settings.data.general.animationSpeed = value
cutoutColor: Color.mSurface
}
NText {
text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
} }
NText { NText {
text: "Fonts" text: Math.floor(Settings.data.general.radiusRatio * 100) + "%"
font.pointSize: Style.fontSizeXXL * scaling Layout.alignment: Qt.AlignVCenter
font.weight: Style.fontWeightBold Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling }
}
}
// Animation Speed
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NLabel {
label: "Animation Speed"
description: "Adjust global animation speed."
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0.1
to: 2.0
stepSize: 0.01
value: Settings.data.general.animationSpeed
onMoved: Settings.data.general.animationSpeed = value
cutoutColor: Color.mSurface
} }
// Font configuration section NText {
ColumnLayout { text: Math.round(Settings.data.general.animationSpeed * 100) + "%"
spacing: Style.marginS * scaling Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
NTextInput {
label: "Default Font"
description: "Main font used throughout the interface."
text: Settings.data.ui.fontDefault
placeholderText: "Roboto"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontDefault = text
}
}
NTextInput {
label: "Fixed Width Font"
description: "Monospace font used for terminal and code display."
text: Settings.data.ui.fontFixed
placeholderText: "DejaVu Sans Mono"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontFixed = text
}
}
NTextInput {
label: "Billboard Font"
description: "Large font used for clocks and prominent displays."
text: Settings.data.ui.fontBillboard
placeholderText: "Inter"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontBillboard = text
}
}
} }
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Fonts
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Fonts"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// Font configuration section
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NTextInput {
label: "Default Font"
description: "Main font used throughout the interface."
text: Settings.data.ui.fontDefault
placeholderText: "Roboto"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontDefault = text
}
}
NTextInput {
label: "Fixed Width Font"
description: "Monospace font used for terminal and code display."
text: Settings.data.ui.fontFixed
placeholderText: "DejaVu Sans Mono"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontFixed = text
}
}
NTextInput {
label: "Billboard Font"
description: "Large font used for clocks and prominent displays."
text: Settings.data.ui.fontBillboard
placeholderText: "Inter"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.ui.fontBillboard = text
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -6,138 +6,104 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root
spacing: 0 ColumnLayout {
spacing: Style.marginL * scaling
ScrollView { NComboBox {
id: scrollView id: launcherPosition
label: "Position"
description: "Choose where the Launcher panel appears."
Layout.fillWidth: true
model: ListModel {
ListElement {
key: "center"
name: "Center (default)"
}
ListElement {
key: "top_left"
name: "Top Left"
}
ListElement {
key: "top_right"
name: "Top Right"
}
ListElement {
key: "bottom_left"
name: "Bottom Left"
}
ListElement {
key: "bottom_right"
name: "Bottom Right"
}
ListElement {
key: "bottom_center"
name: "Bottom Center"
}
ListElement {
key: "top_center"
name: "Top Center"
}
}
currentKey: Settings.data.appLauncher.position
onSelected: function (key) {
Settings.data.appLauncher.position = key
}
}
Layout.fillWidth: true NToggle {
Layout.fillHeight: true label: "Enable Clipboard History"
padding: Style.marginM * scaling description: "Show clipboard history in the launcher."
clip: true checked: Settings.data.appLauncher.enableClipboardHistory
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff onToggled: checked => {
ScrollBar.vertical.policy: ScrollBar.AsNeeded Settings.data.appLauncher.enableClipboardHistory = checked
}
}
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginXXS * scaling
spacing: 0 Layout.fillWidth: true
Item { NText {
Layout.fillWidth: true text: "Background Opacity"
Layout.preferredHeight: 0 font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
} }
ColumnLayout { NText {
spacing: Style.marginL * scaling text: "Adjust the background opacity of the launcher."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
}
NText { RowLayout {
text: "Launcher" NSlider {
font.pointSize: Style.fontSizeXXL * scaling id: launcherBgOpacity
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NToggle {
label: "Enable Clipboard History"
description: "Show clipboard history in the Launcher (command >clip)."
checked: Settings.data.appLauncher.enableClipboardHistory
onToggled: checked => {
Settings.data.appLauncher.enableClipboardHistory = checked
}
}
NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling from: 0.0
Layout.bottomMargin: Style.marginS * scaling to: 1.0
stepSize: 0.01
value: Settings.data.appLauncher.backgroundOpacity
onMoved: Settings.data.appLauncher.backgroundOpacity = value
cutoutColor: Color.mSurface
} }
NText { NText {
text: "Launcher Position" text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
font.pointSize: Style.fontSizeXXL * scaling Layout.alignment: Qt.AlignVCenter
font.weight: Style.fontWeightBold Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NComboBox {
id: launcherPosition
label: "Position"
description: "Choose where the Launcher panel appears."
Layout.fillWidth: true
model: ListModel {
ListElement {
key: "center"
name: "Center (default)"
}
ListElement {
key: "top_left"
name: "Top Left"
}
ListElement {
key: "top_right"
name: "Top Right"
}
ListElement {
key: "bottom_left"
name: "Bottom Left"
}
ListElement {
key: "bottom_right"
name: "Bottom Right"
}
ListElement {
key: "bottom_center"
name: "Bottom Center"
}
ListElement {
key: "top_center"
name: "Top Center"
}
}
currentKey: Settings.data.appLauncher.position
onSelected: function (key) {
Settings.data.appLauncher.position = key
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * scaling
Layout.bottomMargin: Style.marginS * scaling
}
NText {
text: "Launcher Background"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
RowLayout {
NSlider {
id: launcherBgOpacity
Layout.fillWidth: true
from: 0.0
to: 1.0
stepSize: 0.01
value: Settings.data.appLauncher.backgroundOpacity
onMoved: Settings.data.appLauncher.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.appLauncher.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
} }
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -8,69 +8,41 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root spacing: Style.marginL * scaling
spacing: 0
ScrollView { NToggle {
id: scrollView label: "WiFi Enabled"
description: "Enable WiFi connectivity."
checked: Settings.data.network.wifiEnabled
onToggled: checked => {
Settings.data.network.wifiEnabled = checked
NetworkService.setWifiEnabled(checked)
if (checked) {
ToastService.showNotice("WiFi", "Enabled")
} else {
ToastService.showNotice("WiFi", "Disabled")
}
}
}
NToggle {
label: "Bluetooth Enabled"
description: "Enable Bluetooth connectivity."
checked: Settings.data.network.bluetoothEnabled
onToggled: checked => {
Settings.data.network.bluetoothEnabled = checked
BluetoothService.setBluetoothEnabled(checked)
if (checked) {
ToastService.showNotice("Bluetooth", "Enabled")
} else {
ToastService.showNotice("Bluetooth", "Disabled")
}
}
}
NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.topMargin: Style.marginXL * scaling
padding: Style.marginM * scaling Layout.bottomMargin: Style.marginXL * scaling
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Interfaces"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NToggle {
label: "WiFi Enabled"
description: "Enable WiFi connectivity."
checked: Settings.data.network.wifiEnabled
onToggled: checked => {
Settings.data.network.wifiEnabled = checked
NetworkService.setWifiEnabled(checked)
if (checked) {
ToastService.showNotice("WiFi", "Enabled")
} else {
ToastService.showNotice("WiFi", "Disabled")
}
}
}
NToggle {
label: "Bluetooth Enabled"
description: "Enable Bluetooth connectivity."
checked: Settings.data.network.bluetoothEnabled
onToggled: checked => {
Settings.data.network.bluetoothEnabled = checked
BluetoothService.setBluetoothEnabled(checked)
if (checked) {
ToastService.showNotice("Bluetooth", "Enabled")
} else {
ToastService.showNotice("Bluetooth", "Disabled")
}
}
}
}
}
} }
} }

View file

@ -6,297 +6,268 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root spacing: Style.marginL * scaling
spacing: 0
ScrollView {
id: scrollView
// Output Directory
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.topMargin: Style.marginS * scaling
padding: Style.marginM * scaling
clip: true NTextInput {
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff label: "Output Directory"
ScrollBar.vertical.policy: ScrollBar.AsNeeded description: "Directory where screen recordings will be saved."
placeholderText: "/home/xxx/Videos"
text: Settings.data.screenRecorder.directory
onEditingFinished: {
Settings.data.screenRecorder.directory = text
}
Layout.fillWidth: true
}
ColumnLayout { ColumnLayout {
width: scrollView.availableWidth spacing: Style.marginS * scaling
spacing: 0 Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
Item { // Show Cursor
Layout.fillWidth: true NToggle {
Layout.preferredHeight: 0 label: "Show Cursor"
} description: "Record mouse cursor in the video."
checked: Settings.data.screenRecorder.showCursor
ColumnLayout { onToggled: checked => {
spacing: Style.marginXS * scaling Settings.data.screenRecorder.showCursor = checked
Layout.fillWidth: true }
NText {
text: "Recordings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Output Directory
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
NTextInput {
label: "Output Directory"
description: "Directory where screen recordings will be saved."
placeholderText: "/home/xxx/Videos"
text: Settings.data.screenRecorder.directory
onEditingFinished: {
Settings.data.screenRecorder.directory = text
}
}
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
// Show Cursor
NToggle {
label: "Show Cursor"
description: "Record mouse cursor in the video."
checked: Settings.data.screenRecorder.showCursor
onToggled: checked => {
Settings.data.screenRecorder.showCursor = checked
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Video Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Video Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Source
NComboBox {
label: "Video Source"
description: "We recommend using portal, if you get artifacts try screen."
model: ListModel {
ListElement {
key: "portal"
name: "Portal"
}
ListElement {
key: "screen"
name: "Screen"
}
}
currentKey: Settings.data.screenRecorder.videoSource
onSelected: key => {
Settings.data.screenRecorder.videoSource = key
}
}
// Frame Rate
NComboBox {
label: "Frame Rate"
description: "Target frame rate for screen recordings. (default: 60)"
model: ListModel {
ListElement {
key: "30"
name: "30 FPS"
}
ListElement {
key: "60"
name: "60 FPS"
}
ListElement {
key: "100"
name: "100 FPS"
}
ListElement {
key: "120"
name: "120 FPS"
}
ListElement {
key: "144"
name: "144 FPS"
}
ListElement {
key: "165"
name: "165 FPS"
}
ListElement {
key: "240"
name: "240 FPS"
}
}
currentKey: Settings.data.screenRecorder.frameRate
onSelected: key => {
Settings.data.screenRecorder.frameRate = key
}
}
// Video Quality
NComboBox {
label: "Video Quality"
description: "Higher quality results in larger file sizes."
model: ListModel {
ListElement {
key: "medium"
name: "Medium"
}
ListElement {
key: "high"
name: "High"
}
ListElement {
key: "very_high"
name: "Very High"
}
ListElement {
key: "ultra"
name: "Ultra"
}
}
currentKey: Settings.data.screenRecorder.quality
onSelected: key => {
Settings.data.screenRecorder.quality = key
}
}
// Video Codec
NComboBox {
label: "Video Codec"
description: "Different codecs offer different compression and compatibility."
model: ListModel {
ListElement {
key: "h264"
name: "H264"
}
ListElement {
key: "hevc"
name: "HEVC"
}
ListElement {
key: "av1"
name: "AV1"
}
ListElement {
key: "vp8"
name: "VP8"
}
ListElement {
key: "vp9"
name: "VP9"
}
}
currentKey: Settings.data.screenRecorder.videoCodec
onSelected: key => {
Settings.data.screenRecorder.videoCodec = key
}
}
// Color Range
NComboBox {
label: "Color Range"
description: "Limited is recommended for better compatibility."
model: ListModel {
ListElement {
key: "limited"
name: "Limited"
}
ListElement {
key: "full"
name: "Full"
}
}
currentKey: Settings.data.screenRecorder.colorRange
onSelected: key => {
Settings.data.screenRecorder.colorRange = key
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Audio Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Audio Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Audio Source
NComboBox {
label: "Audio Source"
description: "Audio source to capture during recording."
model: ListModel {
ListElement {
key: "default_output"
name: "System Output"
}
ListElement {
key: "default_input"
name: "Microphone Input"
}
ListElement {
key: "both"
name: "System Output + Microphone Input"
}
}
currentKey: Settings.data.screenRecorder.audioSource
onSelected: key => {
Settings.data.screenRecorder.audioSource = key
}
}
// Audio Codec
NComboBox {
label: "Audio Codec"
description: "Opus is recommended for best performance and smallest audio size."
model: ListModel {
ListElement {
key: "opus"
name: "Opus"
}
ListElement {
key: "aac"
name: "AAC"
}
}
currentKey: Settings.data.screenRecorder.audioCodec
onSelected: key => {
Settings.data.screenRecorder.audioCodec = key
}
}
}
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Video Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Video Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// Source
NComboBox {
label: "Video Source"
description: "We recommend using portal, if you get artifacts try screen."
model: ListModel {
ListElement {
key: "portal"
name: "Portal"
}
ListElement {
key: "screen"
name: "Screen"
}
}
currentKey: Settings.data.screenRecorder.videoSource
onSelected: key => {
Settings.data.screenRecorder.videoSource = key
}
}
// Frame Rate
NComboBox {
label: "Frame Rate"
description: "Target frame rate for screen recordings."
model: ListModel {
ListElement {
key: "30"
name: "30 FPS"
}
ListElement {
key: "60"
name: "60 FPS"
}
ListElement {
key: "100"
name: "100 FPS"
}
ListElement {
key: "120"
name: "120 FPS"
}
ListElement {
key: "144"
name: "144 FPS"
}
ListElement {
key: "165"
name: "165 FPS"
}
ListElement {
key: "240"
name: "240 FPS"
}
}
currentKey: Settings.data.screenRecorder.frameRate
onSelected: key => {
Settings.data.screenRecorder.frameRate = key
}
}
// Video Quality
NComboBox {
label: "Video Quality"
description: "Higher quality results in larger file sizes."
model: ListModel {
ListElement {
key: "medium"
name: "Medium"
}
ListElement {
key: "high"
name: "High"
}
ListElement {
key: "very_high"
name: "Very High"
}
ListElement {
key: "ultra"
name: "Ultra"
}
}
currentKey: Settings.data.screenRecorder.quality
onSelected: key => {
Settings.data.screenRecorder.quality = key
}
}
// Video Codec
NComboBox {
label: "Video Codec"
description: "Different codecs offer different compression and compatibility."
model: ListModel {
ListElement {
key: "h264"
name: "H264"
}
ListElement {
key: "hevc"
name: "HEVC"
}
ListElement {
key: "av1"
name: "AV1"
}
ListElement {
key: "vp8"
name: "VP8"
}
ListElement {
key: "vp9"
name: "VP9"
}
}
currentKey: Settings.data.screenRecorder.videoCodec
onSelected: key => {
Settings.data.screenRecorder.videoCodec = key
}
}
// Color Range
NComboBox {
label: "Color Range"
description: "Limited is recommended for better compatibility."
model: ListModel {
ListElement {
key: "limited"
name: "Limited"
}
ListElement {
key: "full"
name: "Full"
}
}
currentKey: Settings.data.screenRecorder.colorRange
onSelected: key => {
Settings.data.screenRecorder.colorRange = key
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Audio Settings
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Audio Settings"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
// Audio Source
NComboBox {
label: "Audio Source"
description: "Audio source to capture during recording."
model: ListModel {
ListElement {
key: "default_output"
name: "System Output"
}
ListElement {
key: "default_input"
name: "Microphone Input"
}
ListElement {
key: "both"
name: "System Output + Microphone Input"
}
}
currentKey: Settings.data.screenRecorder.audioSource
onSelected: key => {
Settings.data.screenRecorder.audioSource = key
}
}
// Audio Codec
NComboBox {
label: "Audio Codec"
description: "Opus is recommended for best performance and smallest audio size."
model: ListModel {
ListElement {
key: "opus"
name: "Opus"
}
ListElement {
key: "aac"
name: "AAC"
}
}
currentKey: Settings.data.screenRecorder.audioCodec
onSelected: key => {
Settings.data.screenRecorder.audioCodec = key
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -6,136 +6,97 @@ import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { ColumnLayout {
id: root
spacing: 0
ScrollView {
id: scrollView
// Location section
NTextInput {
label: "Location name"
description: "Choose a known location near you."
text: Settings.data.location.name
placeholderText: "Enter the location name"
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true onEditingFinished: {
padding: Style.marginM * scaling Settings.data.location.name = text.trim()
clip: true LocationService.resetWeather()
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ColumnLayout {
width: scrollView.availableWidth
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 0
}
ColumnLayout {
spacing: Style.marginXS * scaling
Layout.fillWidth: true
NText {
text: "Location"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
// Location section
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
Layout.topMargin: Style.marginS * scaling
NTextInput {
label: "Location name"
description: "Choose a known location near you."
text: Settings.data.location.name
placeholderText: "Enter the location name"
Layout.fillWidth: true
onEditingFinished: {
Settings.data.location.name = text.trim()
LocationService.resetWeather()
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Time section
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Time Format"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Use 12-Hour Clock"
description: "Display time in 12-hour format (AM/PM) instead of 24-hour."
checked: Settings.data.location.use12HourClock
onToggled: checked => {
Settings.data.location.use12HourClock = checked
}
}
NToggle {
label: "Reverse Day/Month"
description: "Display date as DD/MM instead of MM/DD."
checked: Settings.data.location.reverseDayMonth
onToggled: checked => {
Settings.data.location.reverseDayMonth = checked
}
}
NToggle {
label: "Show Date with Clock"
description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)."
checked: Settings.data.location.showDateWithClock
onToggled: checked => {
Settings.data.location.showDateWithClock = checked
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginL * 2 * scaling
Layout.bottomMargin: Style.marginL * scaling
}
// Weather section
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NText {
text: "Weather"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Use Fahrenheit"
description: "Display temperature in Fahrenheit instead of Celsius."
checked: Settings.data.location.useFahrenheit
onToggled: checked => {
Settings.data.location.useFahrenheit = checked
}
}
}
}
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Time section
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Time Format"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NToggle {
label: "Use 12-Hour Clock"
description: "Display time in 12-hour format (AM/PM) instead of 24-hour."
checked: Settings.data.location.use12HourClock
onToggled: checked => {
Settings.data.location.use12HourClock = checked
}
}
NToggle {
label: "Reverse Day/Month"
description: "Display date as DD/MM instead of MM/DD."
checked: Settings.data.location.reverseDayMonth
onToggled: checked => {
Settings.data.location.reverseDayMonth = checked
}
}
NToggle {
label: "Show Date with Clock"
description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)."
checked: Settings.data.location.showDateWithClock
onToggled: checked => {
Settings.data.location.showDateWithClock = checked
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Weather section
ColumnLayout {
spacing: Style.marginM * scaling
Layout.fillWidth: true
NText {
text: "Weather"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NToggle {
label: "Use Fahrenheit"
description: "Display temperature in Fahrenheit instead of Celsius."
checked: Settings.data.location.useFahrenheit
onToggled: checked => {
Settings.data.location.useFahrenheit = checked
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -6,241 +6,229 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
Item { ColumnLayout {
readonly property real scaling: ScalingService.scale(screen) readonly property real scaling: ScalingService.scale(screen)
readonly property string tabIcon: "photo_library" readonly property string tabIcon: "photo_library"
readonly property string tabLabel: "Wallpaper Selector" readonly property string tabLabel: "Wallpaper Selector"
readonly property int tabIndex: 7 readonly property int tabIndex: 7
Layout.fillWidth: true
Layout.fillHeight: true
ScrollView { spacing: Style.marginL * scaling
anchors.fill: parent
clip: true // Current wallpaper display
ScrollBar.vertical.policy: ScrollBar.AsNeeded NText {
ScrollBar.horizontal.policy: ScrollBar.AsNeeded text: "Current Wallpaper"
contentWidth: parent.width font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 140 * scaling
radius: Style.radiusM * scaling
color: Color.mPrimary
NImageRounded {
id: currentWallpaperImage
anchors.fill: parent
anchors.margins: Style.marginXS * scaling
imagePath: WallpaperService.currentWallpaper
fallbackIcon: "image"
imageRadius: Style.radiusM * scaling
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Wallpaper selector
RowLayout {
Layout.fillWidth: true
ColumnLayout { ColumnLayout {
width: parent.width Layout.fillWidth: true
ColumnLayout {
spacing: Style.marginL * scaling // Wallpaper grid
Layout.margins: Style.marginL * scaling NText {
text: "Wallpaper Selector"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NText {
text: "Click on a wallpaper to set it as your current wallpaper."
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true Layout.fillWidth: true
}
// Current wallpaper display NText {
NText { text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType
text: "Current Wallpaper" + " transition." : "Wallpapers will change instantly."
font.pointSize: Style.fontSizeXXL * scaling color: Color.mOnSurface
font.weight: Style.fontWeightBold font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurface visible: Settings.data.wallpaper.swww.enabled
}
}
NIconButton {
icon: "refresh"
tooltipText: "Refresh wallpaper list"
onClicked: {
WallpaperService.listWallpapers()
}
Layout.alignment: Qt.AlignTop | Qt.AlignRight
}
}
// Wallpaper grid container
Item {
Layout.fillWidth: true
Layout.preferredHeight: {
return Math.ceil(WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
}
GridView {
id: wallpaperGridView
anchors.fill: parent
clip: true
model: WallpaperService.wallpaperList
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.AutoFlickDirection
interactive: false
property int columns: 5
property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns)
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling
leftMargin: Style.marginS * scaling
rightMargin: Style.marginS * scaling
topMargin: Style.marginS * scaling
bottomMargin: Style.marginS * scaling
delegate: Rectangle {
id: wallpaperItem
property string wallpaperPath: modelData
property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper
width: wallpaperGridView.itemSize
height: Math.floor(wallpaperGridView.itemSize * 0.67)
color: Color.transparent
// NImageCached relies on the image being visible to work properly.
// MultiEffect relies on the image being invisible to apply effects.
// That's why we don't have rounded corners here, as we don't want to bring back qt5compat.
NImageCached {
id: img
imagePath: wallpaperPath
anchors.fill: parent
} }
// Borders on top
Rectangle { Rectangle {
Layout.fillWidth: true anchors.fill: parent
Layout.preferredHeight: 140 * scaling color: Color.transparent
radius: Style.radiusM * scaling border.color: isSelected ? Color.mSecondary : Color.mSurface
color: Color.mPrimary border.width: Math.max(1, Style.borderL * 1.5 * scaling)
}
NImageRounded { // Selection tick-mark
id: currentWallpaperImage Rectangle {
anchors.fill: parent anchors.top: parent.top
anchors.margins: Style.marginXS * scaling anchors.right: parent.right
imagePath: WallpaperService.currentWallpaper anchors.margins: Style.marginS * scaling
fallbackIcon: "image" width: 28 * scaling
imageRadius: Style.radiusM * scaling height: 28 * scaling
radius: width / 2
color: Color.mSecondary
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: isSelected
NIcon {
text: "check"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSecondary
anchors.centerIn: parent
} }
} }
NDivider { // Hover effect
Layout.fillWidth: true Rectangle {
Layout.topMargin: Style.marginL * scaling anchors.fill: parent
Layout.bottomMargin: Style.marginL * scaling color: Color.mSurface
} opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.4
radius: parent.radius
RowLayout { Behavior on opacity {
Layout.fillWidth: true NumberAnimation {
duration: Style.animationFast
ColumnLayout {
Layout.fillWidth: true
// Wallpaper grid
NText {
text: "Wallpaper Selector"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
} }
NText {
text: "Click on a wallpaper to set it as your current wallpaper."
color: Color.mOnSurface
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
NText {
text: Settings.data.wallpaper.swww.enabled ? "Wallpapers will change with " + Settings.data.wallpaper.swww.transitionType
+ " transition." : "Wallpapers will change instantly."
color: Color.mOnSurface
font.pointSize: Style.fontSizeXS * scaling
visible: Settings.data.wallpaper.swww.enabled
}
}
NIconButton {
icon: "refresh"
tooltipText: "Refresh wallpaper list"
onClicked: {
WallpaperService.listWallpapers()
}
Layout.alignment: Qt.AlignTop | Qt.AlignRight
} }
} }
// Wallpaper grid container MouseArea {
Item { id: mouseArea
Layout.fillWidth: true anchors.fill: parent
Layout.preferredHeight: { acceptedButtons: Qt.LeftButton
return Math.ceil( hoverEnabled: true
WallpaperService.wallpaperList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight onClicked: {
} WallpaperService.changeWallpaper(wallpaperPath)
GridView {
id: wallpaperGridView
anchors.fill: parent
clip: true
model: WallpaperService.wallpaperList
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.AutoFlickDirection
interactive: false
property int columns: 5
property int itemSize: Math.floor(
(width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns)
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling
leftMargin: Style.marginS * scaling
rightMargin: Style.marginS * scaling
topMargin: Style.marginS * scaling
bottomMargin: Style.marginS * scaling
delegate: Rectangle {
id: wallpaperItem
property string wallpaperPath: modelData
property bool isSelected: wallpaperPath === WallpaperService.currentWallpaper
width: wallpaperGridView.itemSize
height: Math.floor(wallpaperGridView.itemSize * 0.67)
color: Color.transparent
// NImageCached relies on the image being visible to work properly.
// MultiEffect relies on the image being invisible to apply effects.
// That's why we don't have rounded corners here, as we don't want to bring back qt5compat.
NImageCached {
id: img
imagePath: wallpaperPath
anchors.fill: parent
}
// Borders on top
Rectangle {
anchors.fill: parent
color: Color.transparent
border.color: isSelected ? Color.mPrimary : Color.mSurface
border.width: Math.max(1, Style.borderL * scaling)
}
// Selection tick-mark
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Style.marginXS * scaling
width: 28 * scaling
height: 28 * scaling
radius: width / 2
color: Color.mPrimary
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: isSelected
NIcon {
text: "check"
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
anchors.centerIn: parent
}
}
// Hover effect
Rectangle {
anchors.fill: parent
color: Color.mOnSurface
opacity: mouseArea.containsMouse ? 0.1 : 0
radius: parent.radius
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
WallpaperService.changeWallpaper(wallpaperPath)
}
}
}
}
// Empty state
Rectangle {
anchors.fill: parent
color: Color.mSurface
radius: Style.radiusM * scaling
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning
ColumnLayout {
anchors.centerIn: parent
spacing: Style.marginM * scaling
NIcon {
text: "folder_open"
font.pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface
Layout.alignment: Qt.AlignHCenter
}
NText {
text: "No wallpapers found"
color: Color.mOnSurface
font.weight: Style.fontWeightBold
Layout.alignment: Qt.AlignHCenter
}
NText {
text: "Make sure your wallpaper directory is configured and contains image files."
color: Color.mOnSurface
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling
}
}
} }
} }
} }
} }
// Empty state
Rectangle {
anchors.fill: parent
color: Color.mSurface
radius: Style.radiusM * scaling
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
visible: WallpaperService.wallpaperList.length === 0 && !WallpaperService.scanning
ColumnLayout {
anchors.centerIn: parent
spacing: Style.marginM * scaling
NIcon {
text: "folder_open"
font.pointSize: Style.fontSizeL * scaling
color: Color.mOnSurface
Layout.alignment: Qt.AlignHCenter
}
NText {
text: "No wallpapers found"
color: Color.mOnSurface
font.weight: Style.fontWeightBold
Layout.alignment: Qt.AlignHCenter
}
NText {
text: "Make sure your wallpaper directory is configured and contains image files."
color: Color.mOnSurface
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
Layout.preferredWidth: Style.sliderWidth * 1.5 * scaling
}
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -5,10 +5,11 @@ import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
ColumnLayout { RowLayout {
id: root id: root
readonly property real preferredHeight: Style.baseWidgetSize * 1.35 * scaling readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
property real preferredWidth: 320 * scaling
property string label: "" property string label: ""
property string description: "" property string description: ""
@ -23,11 +24,6 @@ ColumnLayout {
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true Layout.fillWidth: true
NLabel {
label: root.label
description: root.description
}
function findIndexByKey(key) { function findIndexByKey(key) {
for (var i = 0; i < root.model.count; i++) { for (var i = 0; i < root.model.count; i++) {
if (root.model.get(i).key === key) { if (root.model.get(i).key === key) {
@ -37,10 +33,15 @@ ColumnLayout {
return -1 return -1
} }
NLabel {
label: root.label
description: root.description
}
ComboBox { ComboBox {
id: combo id: combo
Layout.preferredWidth: 320 * scaling Layout.preferredWidth: root.preferredWidth
Layout.preferredHeight: height Layout.preferredHeight: height
model: model model: model
currentIndex: findIndexByKey(currentKey) currentIndex: findIndexByKey(currentKey)

View file

@ -11,7 +11,7 @@ ColumnLayout {
NText { NText {
text: label text: label
font.pointSize: Style.fontSizeM * scaling font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mOnSurface
visible: label !== "" visible: label !== ""

View file

@ -10,7 +10,6 @@ NBox {
property string sectionName: "" property string sectionName: ""
property var widgetModel: [] property var widgetModel: []
property var availableWidgets: [] property var availableWidgets: []
property var scrollView: null
signal addWidget(string widgetName, string section) signal addWidget(string widgetName, string section)
signal removeWidget(string section, int index) signal removeWidget(string section, int index)
@ -23,7 +22,7 @@ NBox {
if (widgetCount === 0) if (widgetCount === 0)
return 140 * scaling return 140 * scaling
var availableWidth = scrollView ? scrollView.availableWidth - (Style.marginM * scaling * 2) : 400 * scaling var availableWidth = parent.width
var avgWidgetWidth = 150 * scaling var avgWidgetWidth = 150 * scaling
var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth))
var rows = Math.ceil(widgetCount / widgetsPerRow) var rows = Math.ceil(widgetCount / widgetsPerRow)
@ -52,7 +51,7 @@ NBox {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.marginM * scaling anchors.margins: Style.marginL * scaling
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
RowLayout { RowLayout {
@ -189,13 +188,13 @@ NBox {
return return
} }
Logger.log("NSectionEditor", `Started dragging widget: ${modelData} at index ${index}`) //Logger.log("NSectionEditor", `Started dragging widget: ${modelData} at index ${index}`)
// Bring to front when starting drag // Bring to front when starting drag
widgetItem.z = 1000 widgetItem.z = 1000
} }
onReleased: { onReleased: {
Logger.log("NSectionEditor", `Released widget: ${modelData} at index ${index}`) //Logger.log("NSectionEditor", `Released widget: ${modelData} at index ${index}`)
// Reset z-index when drag ends // Reset z-index when drag ends
widgetItem.z = 0 widgetItem.z = 0
@ -232,13 +231,13 @@ NBox {
if (targetIndex !== -1 && targetIndex !== index) { if (targetIndex !== -1 && targetIndex !== index) {
const fromIndex = index const fromIndex = index
const toIndex = targetIndex const toIndex = targetIndex
Logger.log( // Logger.log(
"NSectionEditor", // "NSectionEditor",
`Dropped widget from index ${fromIndex} to position ${toIndex} (distance: ${minDistance.toFixed( // `Dropped widget from index ${fromIndex} to position ${toIndex} (distance: ${minDistance.toFixed(
2)})`) // 2)})`)
reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex)
} else { } else {
Logger.log("NSectionEditor", `No valid drop target found for widget at index ${index}`) Logger.warn("NSectionEditor", `No valid drop target found for widget at index ${index}`)
} }
} }
} }
@ -264,17 +263,16 @@ NBox {
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
} }
onEntered: function (drag) { onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered start drop zone")
Logger.log("NSectionEditor", "Entered start drop zone")
} }
onDropped: function (drop) { onDropped: function (drop) {
Logger.log("NSectionEditor", "Dropped on start zone") //Logger.log("NSectionEditor", "Dropped on start zone")
if (drop.source && drop.source.widgetIndex !== undefined) { if (drop.source && drop.source.widgetIndex !== undefined) {
const fromIndex = drop.source.widgetIndex const fromIndex = drop.source.widgetIndex
const toIndex = 0 // Insert at the beginning const toIndex = 0 // Insert at the beginning
if (fromIndex !== toIndex) { if (fromIndex !== toIndex) {
Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to beginning`)
reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex)
} }
} }
@ -299,17 +297,16 @@ NBox {
radius: Style.radiusS * scaling radius: Style.radiusS * scaling
} }
onEntered: function (drag) { onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered end drop zone")
Logger.log("NSectionEditor", "Entered end drop zone")
} }
onDropped: function (drop) { onDropped: function (drop) {
Logger.log("NSectionEditor", "Dropped on end zone") //Logger.log("NSectionEditor", "Dropped on end zone")
if (drop.source && drop.source.widgetIndex !== undefined) { if (drop.source && drop.source.widgetIndex !== undefined) {
const fromIndex = drop.source.widgetIndex const fromIndex = drop.source.widgetIndex
const toIndex = widgetModel.length // Insert at the end const toIndex = widgetModel.length // Insert at the end
if (fromIndex !== toIndex) { if (fromIndex !== toIndex) {
Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to end`) //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to end`)
reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex) reorderWidget(sectionName.toLowerCase(), fromIndex, toIndex)
} }
} }

View file

@ -30,26 +30,9 @@ RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
ColumnLayout { NLabel {
spacing: Style.marginXXS * scaling label: root.label
Layout.fillWidth: true description: root.description
NText {
text: label
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
visible: label !== ""
}
NText {
text: description
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
visible: description !== ""
}
} }
// Value // Value

View file

@ -150,7 +150,7 @@ Item {
id: labelText id: labelText
text: root.label text: root.label
color: Color.mOnSurface color: Color.mOnSurface
font.pointSize: Style.fontSize * scaling font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
@ -161,7 +161,7 @@ Item {
id: descriptionText id: descriptionText
text: root.description text: root.description
color: Color.mOnSurface color: Color.mOnSurface
font.pointSize: Style.fontSize * scaling font.pointSize: Style.fontSizeM * scaling
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
visible: text.length > 0 visible: text.length > 0
@ -176,7 +176,7 @@ Item {
color: Color.mOnSurface color: Color.mOnSurface
fontPointSize: Style.fontSize * scaling fontPointSize: Style.fontSizeM * scaling
sizeRatio: 0.8 sizeRatio: 0.8
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop

View file

@ -19,24 +19,9 @@ RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
ColumnLayout { NLabel {
spacing: Style.marginXXS * scaling label: root.label
Layout.fillWidth: true description: root.description
NText {
text: label
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: description
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
} }
Rectangle { Rectangle {