This commit is contained in:
2026-05-28 21:45:21 +02:00
parent 653439ac24
commit a356a3d369
610 changed files with 205679 additions and 0 deletions
@@ -0,0 +1,395 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import qs.Commons
import qs.Services.System
import qs.Services.Theming
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginM
function extractSchemeName(path) {
var basename = path.split('/').pop();
return basename.replace('.json', '');
}
// Cache for scheme colors (mirrors ColorSchemeTab approach)
property var schemeColorsCache: ({})
property int cacheVersion: 0
function getSchemeColor(schemeName, key) {
try {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
var data = schemeColorsCache[schemeName];
if (data && data[mode] && data[mode][key])
return data[mode][key];
} catch (e) {}
return Color.mSurfaceVariant;
}
// Match ColorSchemeTab helpers
function schemeLoaded(schemeName, jsonData) {
var value = jsonData || {};
schemeColorsCache[schemeName] = value;
cacheVersion++;
Logger.i("SetupAppearanceStep", `Loaded scheme ${schemeName}`);
}
Connections {
target: ColorSchemeService
function onSchemesChanged() {
Logger.i("SetupAppearanceStep", `Color schemes changed: ${ColorSchemeService.schemes.length}`);
schemeColorsCache = {};
cacheVersion++;
}
}
// Beautiful header with icon
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL
spacing: Style.marginM
Rectangle {
width: 40
height: 40
radius: Style.radiusL
color: Color.mSurfaceVariant
opacity: 0.6
NIcon {
icon: "palette"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXS
NText {
text: I18n.tr("common.appearance")
pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NText {
text: I18n.tr("setup.appearance.subheader")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
}
NScrollView {
id: appearanceScrollView
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
ColumnLayout {
width: appearanceScrollView.availableWidth
spacing: Style.marginM
// Dark Mode Toggle
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Rectangle {
width: 28
height: 28
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "moon"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("tooltips.switch-to-dark-mode")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.color-scheme.dark-mode-switch-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NToggle {
checked: Settings.data.colorSchemes.darkMode
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Wallpaper Colors Toggle
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Rectangle {
width: 28
height: 28
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "color-picker"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.color-scheme.color-source-use-wallpaper-colors-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NToggle {
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
Settings.data.colorSchemes.useWallpaperColors = true;
AppThemeService.generate();
} else {
Settings.data.colorSchemes.useWallpaperColors = false;
if (Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
}
}
}
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
visible: !Settings.data.colorSchemes.useWallpaperColors
}
// Predefined schemes section (visible when wallpaper colors disabled)
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
visible: !Settings.data.colorSchemes.useWallpaperColors
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Rectangle {
width: 28
height: 28
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "palette"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.color-scheme.predefined-title")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.color-scheme.predefined-desc")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
}
}
}
// Predefined schemes Grid (matches ColorSchemeTab)
GridLayout {
id: schemesGrid
columns: Math.max(2, Math.floor((parent.width - Style.margin2M) / 180))
rowSpacing: Style.marginM
columnSpacing: Style.marginM
Layout.fillWidth: true
Repeater {
model: ColorSchemeService.schemes
delegate: Rectangle {
id: schemeItem
property string schemePath: modelData
property string schemeName: root.extractSchemeName(modelData)
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
height: 50
radius: Style.radiusS
color: root.cacheVersion >= 0 ? root.getSchemeColor(schemeName, "mSurface") : root.getSchemeColor(schemeName, "mSurface")
border.width: Style.borderL
border.color: itemMouseArea.containsMouse ? Color.mHover : (Settings.data.colorSchemes.predefinedScheme === schemeName ? Color.mSecondary : Color.mOutline)
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginXS
NText {
text: schemeItem.schemeName
pointSize: Style.fontSizeS
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
maximumLineCount: 1
}
Rectangle {
width: 14
height: 14
radius: width * 0.5
color: root.cacheVersion >= 0 ? (function () {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
var cached = root.schemeColorsCache[schemeItem.schemeName];
return (cached && cached[mode] && cached[mode].mPrimary) || root.getSchemeColor(schemeItem.schemeName, "mPrimary");
})() : Color.mPrimary
}
Rectangle {
width: 14
height: 14
radius: width * 0.5
color: root.cacheVersion >= 0 ? (function () {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
var cached = root.schemeColorsCache[schemeItem.schemeName];
return (cached && cached[mode] && cached[mode].mSecondary) || root.getSchemeColor(schemeItem.schemeName, "mSecondary");
})() : Color.mSecondary
}
Rectangle {
width: 14
height: 14
radius: width * 0.5
color: root.cacheVersion >= 0 ? (function () {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
var cached = root.schemeColorsCache[schemeItem.schemeName];
return (cached && cached[mode] && cached[mode].mTertiary) || root.getSchemeColor(schemeItem.schemeName, "mTertiary");
})() : Color.mTertiary
}
Rectangle {
width: 14
height: 14
radius: width * 0.5
color: root.cacheVersion >= 0 ? (function () {
var mode = Settings.data.colorSchemes.darkMode ? "dark" : "light";
var cached = root.schemeColorsCache[schemeItem.schemeName];
return (cached && cached[mode] && cached[mode].mError) || root.getSchemeColor(schemeItem.schemeName, "mError");
})() : Color.mError
}
}
MouseArea {
id: itemMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.data.colorSchemes.useWallpaperColors = false;
Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName;
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme);
}
}
}
}
}
}
// Bottom spacer
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginL
}
}
}
// Hidden loader to populate schemeColorsCache from files
Item {
visible: false
Repeater {
model: ColorSchemeService.schemes
delegate: Item {
FileView {
path: modelData
blockLoading: false
onLoaded: {
var schemeName = root.extractSchemeName(path);
try {
var jsonData = JSON.parse(text());
root.schemeLoaded(schemeName, jsonData);
} catch (e) {
root.schemeLoaded(schemeName, null);
}
}
}
}
}
}
}
@@ -0,0 +1,617 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property real selectedScaleRatio: 1.0
property string selectedBarPosition: "top"
signal scaleRatioChanged(real ratio)
signal barPositionChanged(string position)
spacing: Style.marginM
// Beautiful header with icon
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL
spacing: Style.marginM
Rectangle {
width: 40
height: 40
radius: Style.radiusL
color: Color.mSurfaceVariant
opacity: 0.6
NIcon {
icon: "palette"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXS
NText {
text: I18n.tr("setup.customize.header")
pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NText {
text: I18n.tr("setup.customize.subheader")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
}
NScrollView {
id: customizeScrollView
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
ColumnLayout {
width: customizeScrollView.availableWidth
spacing: Style.marginM
// Bar Position section
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Rectangle {
width: 28
height: 28
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "layout-2"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.bar.appearance-position-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.bar.appearance-position-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Repeater {
model: [
{
"key": "top",
"name": I18n.tr("positions.top"),
"icon": "arrow-up"
},
{
"key": "bottom",
"name": I18n.tr("positions.bottom"),
"icon": "arrow-down"
},
{
"key": "left",
"name": I18n.tr("positions.left"),
"icon": "arrow-left"
},
{
"key": "right",
"name": I18n.tr("positions.right"),
"icon": "arrow-right"
}
]
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: Style.radiusM
border.width: Style.borderS
property bool isActive: selectedBarPosition === modelData.key
color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant
border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline
opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8
NText {
text: modelData.name
pointSize: Style.fontSizeM
font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium
color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface
anchors.centerIn: parent
}
HoverHandler {
id: hoverHandler
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
selectedBarPosition = modelData.key;
barPositionChanged(modelData.key);
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Bar Density section
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Rectangle {
width: 32
height: 32
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "minimize"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.bar.appearance-density-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.bar.appearance-density-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
RowLayout {
spacing: Style.marginS
Repeater {
model: [
{
"key": "mini",
"name": I18n.tr("options.bar.density-mini")
},
{
"key": "compact",
"name": I18n.tr("options.bar.density-compact")
},
{
"key": "default",
"name": I18n.tr("options.bar.density-default")
},
{
"key": "comfortable",
"name": I18n.tr("options.bar.density-comfortable")
},
{
"key": "spacious",
"name": I18n.tr("options.bar.density-spacious")
}
]
delegate: Rectangle {
radius: Style.radiusM
border.width: Style.borderS
Layout.preferredHeight: 32
Layout.preferredWidth: Math.max(90, densityText.implicitWidth + Style.margin2XL)
property bool isActive: Settings.data.bar.density === modelData.key
color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant
border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline
opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8
NText {
id: densityText
text: modelData.name
pointSize: Style.fontSizeS
font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium
color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface
anchors.centerIn: parent
}
HoverHandler {
id: hoverHandler
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.data.bar.density = modelData.key;
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// UI Scale section
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Rectangle {
width: 32
height: 32
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "maximize"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.user-interface.scaling-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.user-interface.scaling-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
}
}
}
NValueSlider {
Layout.fillWidth: true
from: 0.8
to: 1.2
stepSize: 0.05
value: selectedScaleRatio
onMoved: function (value) {
selectedScaleRatio = value;
scaleRatioChanged(value);
}
text: Math.floor(selectedScaleRatio * 100) + "%"
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Bar Type section
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Rectangle {
width: 28
height: 28
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "layout-2"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.bar.appearance-type-label") ?? "Bar Type"
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.bar.appearance-type-description") ?? "Choose the style of the bar: Simple, Floating or Framed"
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Repeater {
model: [
{
"key": "simple",
"name": I18n.tr("options.bar.type-simple") ?? "Simple"
},
{
"key": "floating",
"name": I18n.tr("options.bar.type-floating") ?? "Floating"
},
{
"key": "framed",
"name": I18n.tr("options.bar.type-framed") ?? "Framed"
}
]
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
radius: Style.radiusM
border.width: Style.borderS
property bool isActive: Settings.data.bar.barType === modelData.key
color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mSurfaceVariant
border.color: (hoverHandler.hovered || isActive) ? Color.mPrimary : Color.mOutline
opacity: (hoverHandler.hovered || isActive) ? 1.0 : 0.8
NText {
text: modelData.name
pointSize: Style.fontSizeM
font.weight: (hoverHandler.hovered || parent.isActive) ? Style.fontWeightBold : Style.fontWeightMedium
color: (hoverHandler.hovered || parent.isActive) ? Color.mOnPrimary : Color.mOnSurface
anchors.centerIn: parent
}
HoverHandler {
id: hoverHandler
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
Settings.data.bar.barType = modelData.key;
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
}
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Dim Desktop opacity
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Rectangle {
width: 32
height: 32
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "screen-share"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.user-interface.dimmer-opacity-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.user-interface.dimmer-opacity-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
NValueSlider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXS
from: 0
to: 1
stepSize: 0.01
value: Settings.data.general.dimmerOpacity
onMoved: value => Settings.data.general.dimmerOpacity = value
text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%"
}
}
}
// Divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
// Drop Shadows toggle
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Rectangle {
width: 32
height: 32
radius: Style.radiusM
color: Color.mSurface
NIcon {
icon: "shadow"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: I18n.tr("panels.user-interface.shadows-label")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: I18n.tr("panels.user-interface.shadows-description")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NToggle {
checked: Settings.data.general.enableShadows
onToggled: checked => Settings.data.general.enableShadows = checked
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginL
}
}
}
}
@@ -0,0 +1,210 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Compositor
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginM
// Header
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL
spacing: Style.marginM
Rectangle {
width: 40
height: 40
radius: Style.radiusL
color: Color.mSurfaceVariant
opacity: 0.6
NIcon {
icon: "device-desktop"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXS
NText {
text: I18n.tr("panels.dock.title") || "Dock"
pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NText {
text: I18n.tr("panels.dock.monitors-desc")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
}
// Options
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginL
NToggle {
Layout.fillWidth: true
label: I18n.tr("panels.dock.enabled-label")
description: I18n.tr("panels.dock.enabled-description")
checked: Settings.data.dock.enabled
onToggled: checked => Settings.data.dock.enabled = checked
}
// Display behavior
NComboBox {
visible: Settings.data.dock.enabled
Layout.fillWidth: true
label: I18n.tr("panels.display.title")
description: I18n.tr("panels.dock.appearance-display-description")
model: [
{
"key": "always_visible",
"name": I18n.tr("hide-modes.visible")
},
{
"key": "auto_hide",
"name": I18n.tr("panels.dock.appearance-display-auto-hide")
},
{
"key": "exclusive",
"name": I18n.tr("panels.dock.appearance-display-exclusive")
}
]
currentKey: Settings.data.dock.displayMode
onSelected: key => Settings.data.dock.displayMode = key
}
// Background opacity
ColumnLayout {
visible: Settings.data.dock.enabled
spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
label: I18n.tr("panels.osd.background-opacity-label")
description: I18n.tr("panels.dock.appearance-background-opacity-description")
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: value => Settings.data.dock.backgroundOpacity = value
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
}
}
// Floating distance
ColumnLayout {
visible: Settings.data.dock.enabled
spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
label: I18n.tr("panels.dock.appearance-floating-distance-label")
description: I18n.tr("panels.dock.appearance-floating-distance-description")
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 4
stepSize: 0.01
value: Settings.data.dock.floatingRatio
onMoved: value => Settings.data.dock.floatingRatio = value
text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%"
}
}
// Icon size
ColumnLayout {
visible: Settings.data.dock.enabled
spacing: Style.marginXXS
Layout.fillWidth: true
NLabel {
label: I18n.tr("panels.dock.appearance-icon-size-label")
description: I18n.tr("panels.dock.appearance-icon-size-description")
}
NValueSlider {
Layout.fillWidth: true
from: 0
to: 2
stepSize: 0.01
value: Settings.data.dock.size
onMoved: value => Settings.data.dock.size = value
text: Math.floor(Settings.data.dock.size * 100) + "%"
}
}
NToggle {
visible: Settings.data.dock.enabled
Layout.fillWidth: true
label: I18n.tr("panels.dock.monitors-only-same-monitor-label")
description: I18n.tr("panels.dock.monitors-only-same-monitor-description")
checked: Settings.data.dock.onlySameOutput
onToggled: checked => Settings.data.dock.onlySameOutput = checked
}
NToggle {
visible: Settings.data.dock.enabled
Layout.fillWidth: true
label: I18n.tr("panels.dock.appearance-colorize-icons-label")
description: I18n.tr("panels.dock.appearance-colorize-icons-description")
checked: Settings.data.dock.colorizeIcons
onToggled: checked => Settings.data.dock.colorizeIcons = checked
}
NHeader {
visible: Settings.data.dock.enabled
label: I18n.tr("panels.dock.monitors-title")
description: I18n.tr("panels.dock.monitors-desc")
}
Repeater {
visible: Settings.data.dock.enabled
model: Quickshell.screens || []
delegate: NCheckbox {
Layout.fillWidth: true
readonly property real compositorScale: {
const info = CompositorService.displayScales[modelData.name];
return (info && info.scale) ? info.scale : 1.0;
}
label: modelData.name || "Unknown"
visible: Settings.data.dock.enabled
description: {
I18n.tr("system.monitor-description", {
"model": modelData.model,
"width": modelData.width * compositorScale,
"height": modelData.height * compositorScale,
"scale": compositorScale
});
}
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
onToggled: checked => {
if (checked) {
const arr = (Settings.data.dock.monitors || []).slice();
if (arr.indexOf(modelData.name) === -1)
arr.push(modelData.name);
Settings.data.dock.monitors = arr;
} else {
Settings.data.dock.monitors = (Settings.data.dock.monitors || []).filter(function (n) {
return n !== modelData.name;
});
}
}
}
}
}
}
@@ -0,0 +1,463 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
property string selectedDirectory: ""
property string selectedWallpaper: ""
property string previewCachedPath: ""
signal directoryChanged(string directory)
signal wallpaperChanged(string wallpaper)
onSelectedWallpaperChanged: {
if (!selectedWallpaper) {
previewCachedPath = "";
return;
}
if (ImageCacheService.initialized) {
ImageCacheService.getLarge(selectedWallpaper, Screen.width, Screen.height, function (path, success) {
if (root)
root.previewCachedPath = success ? path : selectedWallpaper;
});
} else {
previewCachedPath = selectedWallpaper;
}
}
spacing: Style.marginL
// Beautiful header with icon
ColumnLayout {
Layout.fillWidth: true
Layout.bottomMargin: Style.marginL
spacing: Style.marginM
RowLayout {
spacing: Style.marginM
Rectangle {
width: 40
height: 40
radius: Style.radiusL
color: Color.mSurfaceVariant
opacity: 0.6
NIcon {
icon: "image"
pointSize: Style.fontSizeL
color: Color.mPrimary
anchors.centerIn: parent
}
}
ColumnLayout {
spacing: Style.marginXS
NText {
text: I18n.tr("setup.wallpaper.header")
pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mPrimary
}
NText {
text: I18n.tr("setup.wallpaper.subheader")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
}
}
// Large preview area
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 180
color: Color.mSurfaceVariant
radius: Style.radiusL
// Image with rounded corners
NImageRounded {
anchors.fill: parent
visible: previewCachedPath !== ""
imagePath: previewCachedPath
radius: Style.radiusL
borderColor: previewCachedPath !== "" ? Color.mPrimary : Color.mOutline
borderWidth: previewCachedPath !== "" ? 2 : 1
imageFillMode: Image.PreserveAspectCrop
}
ColumnLayout {
anchors.centerIn: parent
spacing: Style.marginL
visible: selectedWallpaper === ""
Rectangle {
Layout.alignment: Qt.AlignHCenter
width: 64
height: 64
radius: width / 2
color: Color.mPrimary
NIcon {
icon: "sparkles"
pointSize: Style.fontSizeXXL
color: Color.mOnPrimary
anchors.centerIn: parent
}
}
NText {
text: I18n.tr("setup.wallpaper.select-prompt")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
font.weight: Style.fontWeightMedium
}
}
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
}
// Wallpaper gallery strip
Item {
Layout.fillWidth: true
Layout.preferredHeight: 92
visible: filteredWallpapers.length > 0
NScrollView {
id: galleryScroll
anchors.fill: parent
horizontalPolicy: ScrollBar.AsNeeded
verticalPolicy: ScrollBar.AlwaysOff
showGradientMasks: false
reserveScrollbarSpace: false
// Enable vertical mouse wheel to scroll the horizontal strip by moving contentX
WheelHandler {
target: galleryScroll.contentItem
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: event => {
const flick = galleryScroll.contentItem;
if (!flick)
return;
const delta = event.pixelDelta.x !== 0 || event.pixelDelta.y !== 0 ? (event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.pixelDelta.x) : (event.angleDelta.y !== 0 ? event.angleDelta.y : event.angleDelta.x);
// Move opposite of wheel to scroll content to the right for wheel down
const step = -delta;
const maxX = Math.max(0, flick.contentWidth - flick.width);
let newX = flick.contentX + step;
if (newX < 0)
newX = 0;
if (newX > maxX)
newX = maxX;
flick.contentX = newX;
event.accepted = true;
}
}
RowLayout {
spacing: Style.marginM
height: parent.height
Repeater {
model: filteredWallpapers
delegate: Item {
id: thumbDelegate
readonly property int baseWidth: 120
readonly property int baseHeight: 80
readonly property int thumbRadius: Style.radiusM
property string cachedPath: ""
Layout.preferredWidth: baseWidth
Layout.preferredHeight: baseHeight
Component.onCompleted: {
if (ImageCacheService.initialized) {
ImageCacheService.getThumbnail(modelData, function (path, success) {
if (thumbDelegate)
thumbDelegate.cachedPath = success ? path : modelData;
});
} else {
cachedPath = modelData;
}
}
// Rounded thumbnail using NImageRounded with caching
NImageRounded {
id: thumbImage
anchors.fill: parent
imagePath: thumbDelegate.cachedPath
radius: thumbDelegate.thumbRadius
borderColor: selectedWallpaper === modelData ? Color.mPrimary : Color.mOutline
borderWidth: selectedWallpaper === modelData ? 2 : 1
imageFillMode: Image.PreserveAspectCrop
}
// Loading/error state background
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
radius: thumbDelegate.thumbRadius
visible: thumbImage.status === Image.Loading || thumbImage.status === Image.Error || thumbDelegate.cachedPath === ""
NIcon {
icon: "image"
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
anchors.centerIn: parent
}
}
NBusyIndicator {
anchors.centerIn: parent
visible: thumbImage.status === Image.Loading || thumbDelegate.cachedPath === ""
running: visible
size: 18
}
// Hover overlay
Rectangle {
anchors.fill: parent
radius: thumbDelegate.thumbRadius
color: Color.mPrimary
opacity: hoverHandler.hovered ? 0.1 : 0
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
}
// Selection badge
Rectangle {
visible: selectedWallpaper === modelData
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 6
width: 24
height: 24
radius: width / 2
color: Color.mPrimary
NIcon {
icon: "check"
pointSize: Style.fontSizeS
color: Color.mOnPrimary
anchors.centerIn: parent
}
}
HoverHandler {
id: hoverHandler
}
TapHandler {
onTapped: {
selectedWallpaper = modelData;
wallpaperChanged(modelData);
}
}
}
}
}
}
}
// Helpful info card
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
color: Color.mSurfaceVariant
radius: Style.radiusM
opacity: 0.4
visible: filteredWallpapers.length === 0
RowLayout {
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
NIcon {
icon: "folder-open"
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXS
NText {
text: filteredWallpapers.length === 0 && selectedDirectory !== "" ? I18n.tr("setup.wallpaper.none-in-dir") : I18n.tr("setup.wallpaper.no-dir")
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Color.mOnSurfaceVariant
}
NText {
text: selectedDirectory !== "" ? I18n.tr("setup.wallpaper.no-valid", {
"dir": selectedDirectory
}) : I18n.tr("setup.wallpaper.choose-dir")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
opacity: 0.8
}
}
}
}
// Directory selection
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
NTextInputButton {
id: wallpaperPathInput
label: I18n.tr("setup.wallpaper.dir-label")
description: I18n.tr("setup.wallpaper.dir-description")
text: selectedDirectory
buttonIcon: "folder-open"
buttonTooltip: I18n.tr("setup.wallpaper.dir-browse")
Layout.fillWidth: true
onInputTextChanged: text => {
selectedDirectory = text;
directoryChanged(text);
}
onButtonClicked: directoryPicker.open()
}
}
// Internal properties and functions
property list<string> wallpapersList: []
property list<string> filteredWallpapers: []
function updateFilteredWallpapers() {
filteredWallpapers = wallpapersList;
}
function refreshWallpapers() {
if (!selectedDirectory || selectedDirectory === "") {
wallpapersList = [];
filteredWallpapers = [];
return;
}
if (typeof WallpaperService !== "undefined" && WallpaperService.getWallpapersList) {
var wallpapers = WallpaperService.getWallpapersList(Screen.name);
wallpapersList = wallpapers;
updateFilteredWallpapers();
if (wallpapersList.length > 0 && selectedWallpaper === "") {
selectedWallpaper = wallpapersList[0];
}
} else {
readDirectoryImages(selectedDirectory);
}
}
function readDirectoryImages(directoryPath) {
// Build find command args dynamically from ImageCacheService filters
var filters = ImageCacheService.imageFilters;
var findArgs = ["find", directoryPath, "-type", "f", "("];
for (var i = 0; i < filters.length; i++) {
if (i > 0) {
findArgs.push("-o");
}
findArgs.push("-iname");
findArgs.push(filters[i]);
}
findArgs.push(")");
directoryScanner.command = findArgs;
directoryScanner.running = true;
return [];
}
onSelectedDirectoryChanged: {
if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper) {
Settings.data.wallpaper.directory = selectedDirectory;
}
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
WallpaperService.refreshWallpapersList();
}
Qt.callLater(refreshWallpapers);
}
Connections {
target: WallpaperService
enabled: typeof WallpaperService !== "undefined"
function onWallpaperListChanged(screenName, count) {
if (screenName === Screen.name) {
Qt.callLater(refreshWallpapers);
}
}
}
Timer {
id: initialRefreshTimer
interval: 1000
running: false
repeat: false
onTriggered: refreshWallpapers()
}
Component.onCompleted: {
if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper && Settings.data.wallpaper.directory) {
selectedDirectory = Settings.data.wallpaper.directory;
} else {
selectedDirectory = Quickshell.env("HOME") + "/Pictures/Wallpapers";
}
if (typeof WallpaperService !== "undefined" && WallpaperService.currentWallpaper) {
selectedWallpaper = WallpaperService.currentWallpaper;
}
initialRefreshTimer.start();
}
NFilePicker {
id: directoryPicker
selectionMode: "folders"
title: I18n.tr("setup.wallpaper.dir-select-title")
initialPath: selectedDirectory || Quickshell.env("HOME") + "/Pictures"
onAccepted: paths => {
if (paths.length > 0) {
selectedDirectory = paths[0];
directoryChanged(paths[0]);
}
}
}
Process {
id: directoryScanner
command: []
running: false
stdout: StdioCollector {}
stderr: StdioCollector {}
onExited: function (exitCode) {
if (exitCode === 0) {
var lines = stdout.text.split('\n');
var images = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line !== '') {
images.push(line);
}
}
wallpapersList = images;
updateFilteredWallpapers();
if (wallpapersList.length > 0 && selectedWallpaper === "") {
selectedWallpaper = wallpapersList[0];
}
}
}
}
}
@@ -0,0 +1,477 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Noctalia
import qs.Services.System
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
// When true, only shows step 0 with modified text for returning users (telemetry notification)
property bool telemetryOnlyMode: false
signal telemetryWizardCompleted
preferredWidth: Math.round(preferredWidthRatio * 2560 * Style.uiScaleRatio)
preferredHeight: Math.round(preferredHeightRatio * 1440 * Style.uiScaleRatio)
preferredWidthRatio: 0.4
preferredHeightRatio: root.telemetryOnlyMode ? 0.45 : 0.6
panelAnchorHorizontalCenter: true
panelAnchorVerticalCenter: true
closeWithEscape: false
panelContent: Item {
id: panelContent
// Wizard state (lazy-loaded with panelContent)
property int currentStep: 0
readonly property int totalSteps: root.telemetryOnlyMode ? 1 : 5
property bool isCompleting: false
// Setup wizard data
property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory
property string selectedWallpaper: ""
property real selectedScaleRatio: 1.0
property string selectedBarPosition: "top"
Component.onCompleted: {
selectedScaleRatio = Settings.data.general.scaleRatio;
selectedBarPosition = Settings.data.bar.position;
selectedWallpaperDirectory = Settings.data.wallpaper.directory || Settings.defaultWallpapersDirectory;
}
Connections {
target: Settings
function onSettingsSaved() {
if (panelContent.isCompleting) {
Logger.i("SetupWizard", "Settings saved, closing panel");
panelContent.isCompleting = false;
root.close();
}
}
}
Timer {
id: closeTimer
interval: 2000
onTriggered: {
if (panelContent.isCompleting) {
Logger.w("SetupWizard", "Settings save timeout, closing panel anyway");
panelContent.isCompleting = false;
root.close();
}
}
}
function completeSetup() {
if (isCompleting) {
Logger.w("SetupWizard", "completeSetup() called while already completing, ignoring");
return;
}
try {
Logger.i("SetupWizard", root.telemetryOnlyMode ? "Completing telemetry wizard" : "Completing setup with selected options");
isCompleting = true;
// In telemetry-only mode, we only need to save the telemetry setting
if (!root.telemetryOnlyMode) {
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
Settings.data.wallpaper.directory = selectedWallpaperDirectory;
WallpaperService.refreshWallpapersList();
}
if (selectedWallpaper !== "") {
WallpaperService.changeWallpaper(selectedWallpaper, undefined);
}
}
Settings.data.general.scaleRatio = selectedScaleRatio;
Settings.data.bar.position = selectedBarPosition;
}
// Mark the current version as seen to prevent telemetry wizard on next startup
// (only for full setup wizard - telemetry wizard lets changelog mark it seen)
if (!root.telemetryOnlyMode) {
UpdateService.markChangelogSeen(UpdateService.currentVersion);
}
// Initialize telemetry now that user has made their choice
TelemetryService.init();
// Save settings immediately and wait for settingsSaved signal before closing
Settings.saveImmediate();
Logger.i("SetupWizard", "Setup completed successfully, waiting for settings save confirmation");
// Emit signal for telemetry wizard completion (shell.qml will show changelog)
if (root.telemetryOnlyMode) {
root.telemetryWizardCompleted();
}
// Fallback: if settingsSaved signal doesn't fire within 2 seconds, close anyway
closeTimer.start();
} catch (error) {
Logger.e("SetupWizard", "Error completing setup:", error);
isCompleting = false;
}
}
function applyWallpaperSettings() {
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
Settings.data.wallpaper.directory = selectedWallpaperDirectory;
WallpaperService.refreshWallpapersList();
}
if (selectedWallpaper !== "") {
WallpaperService.changeWallpaper(selectedWallpaper, undefined);
}
}
}
function applyUISettings() {
Settings.data.general.scaleRatio = selectedScaleRatio;
Settings.data.bar.position = selectedBarPosition;
}
ColumnLayout {
id: wizardContent
anchors.fill: parent
anchors.margins: Style.marginXL
spacing: Style.marginL
// Step content - takes most of the space
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: Math.round(300 * Style.uiScaleRatio)
StackLayout {
id: stepStack
anchors.fill: parent
currentIndex: currentStep
// Step 0: Welcome - Beautiful centered design
Item {
ColumnLayout {
anchors.centerIn: parent
width: Math.round(Math.max(parent.width * 0.5, 420))
spacing: Style.marginXL
// Logo with subtle glow effect
Item {
Layout.fillWidth: true
Layout.preferredHeight: 120
Layout.alignment: Qt.AlignHCenter
Rectangle {
anchors.centerIn: parent
width: 120
height: 120
radius: width / 2
color: Color.mPrimary
opacity: 0.08
scale: 1.3
}
Image {
anchors.centerIn: parent
width: 110
height: 110
source: Qt.resolvedUrl(Quickshell.shellDir + "/Assets/noctalia.svg")
fillMode: Image.PreserveAspectFit
smooth: true
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
radius: width / 2
border.color: Color.mOutline
border.width: Style.borderM
visible: parent.status === Image.Error
NIcon {
icon: "sparkles"
pointSize: Style.fontSizeXXL * 1.5
color: Color.mPrimary
anchors.centerIn: parent
}
}
// Subtle pulse animation
SequentialAnimation on scale {
running: true
loops: Animation.Infinite
NumberAnimation {
from: 1.0
to: 1.05
duration: 2000
easing.type: Easing.InOutQuad
}
NumberAnimation {
from: 1.05
to: 1.0
duration: 2000
easing.type: Easing.InOutQuad
}
}
}
}
// Welcome text with gradient feel
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginM
NText {
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-title") : I18n.tr("setup.welcome-title")
pointSize: Style.fontSizeXXL * 1.4
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
NText {
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-subtitle") : I18n.tr("setup.welcome-subtitle")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
// Friendly subtext
Rectangle {
Layout.fillWidth: true
Layout.topMargin: Style.marginL
Layout.preferredHeight: childrenRect.height + Style.margin2M
color: Color.mSurfaceVariant
radius: Style.radiusL
NText {
anchors.centerIn: parent
width: parent.width - Style.margin2L
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-note") : I18n.tr("setup.welcome-note")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
// Telemetry toggle
NToggle {
Layout.fillWidth: true
Layout.topMargin: Style.marginM
label: I18n.tr("panels.about.telemetry-enabled")
description: I18n.tr("panels.about.telemetry-desc")
checked: Settings.data.general.telemetryEnabled
onToggled: checked => Settings.data.general.telemetryEnabled = checked
}
}
}
}
// Step 1: Wallpaper Setup
SetupWallpaperStep {
id: step1
selectedDirectory: panelContent.selectedWallpaperDirectory
selectedWallpaper: panelContent.selectedWallpaper
onDirectoryChanged: function (directory) {
panelContent.selectedWallpaperDirectory = directory;
panelContent.applyWallpaperSettings();
}
onWallpaperChanged: function (wallpaper) {
panelContent.selectedWallpaper = wallpaper;
panelContent.applyWallpaperSettings();
}
}
// Step 2: Appearance - Dark mode and color source
SetupAppearanceStep {
id: step3
}
// Step 3: UI Configuration
SetupCustomizeStep {
id: step2
selectedScaleRatio: panelContent.selectedScaleRatio
selectedBarPosition: panelContent.selectedBarPosition
onScaleRatioChanged: function (ratio) {
panelContent.selectedScaleRatio = ratio;
panelContent.applyUISettings();
}
onBarPositionChanged: function (position) {
panelContent.selectedBarPosition = position;
panelContent.applyUISettings();
}
}
// Step 4: Dock Setup
SetupDockStep {
id: stepDock
}
}
}
// Elegant divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
opacity: 0.2
visible: !root.telemetryOnlyMode
}
// Modern progress indicator with labels
Item {
Layout.fillWidth: true
Layout.preferredHeight: 32
visible: !root.telemetryOnlyMode
RowLayout {
anchors.centerIn: parent
spacing: Style.marginM
Repeater {
model: [
{
"icon": "sparkles",
"label": I18n.tr("setup.welcome")
},
{
"icon": "image",
"label": I18n.tr("common.wallpaper")
},
{
"icon": "palette",
"label": I18n.tr("common.appearance")
},
{
"icon": "settings",
"label": I18n.tr("common.customize")
},
{
"icon": "device-desktop",
"label": I18n.tr("panels.dock.title")
}
]
delegate: RowLayout {
spacing: Style.marginS
Rectangle {
width: 24
height: 24
radius: width / 2
color: index <= currentStep ? Color.mPrimary : Color.mSurfaceVariant
border.color: index === currentStep ? Color.mPrimary : "transparent"
border.width: index === currentStep ? 2 : 0
NIcon {
icon: modelData.icon
pointSize: Style.fontSizeS
color: index <= currentStep ? Color.mOnPrimary : Color.mOnSurfaceVariant
anchors.centerIn: parent
}
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
NText {
text: modelData.label
pointSize: Style.fontSizeS
color: index <= currentStep ? Color.mPrimary : Color.mOnSurfaceVariant
font.weight: index === currentStep ? Style.fontWeightBold : Style.fontWeightRegular
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
// Connector line
Rectangle {
width: 40
height: 2
radius: 1
color: index < currentStep ? Color.mPrimary : Color.mSurfaceVariant
visible: index < totalSteps - 1
Behavior on color {
ColorAnimation {
duration: Style.animationNormal
}
}
}
}
}
}
}
// Smooth navigation buttons
Item {
Layout.fillWidth: true
Layout.preferredHeight: 44
Layout.topMargin: Style.marginS
RowLayout {
anchors.fill: parent
spacing: Style.marginM
NButton {
text: I18n.tr("setup.skip-setup")
outlined: true
visible: !root.telemetryOnlyMode
Layout.preferredHeight: 44
onClicked: {
panelContent.completeSetup();
}
}
Item {
Layout.fillWidth: true
}
NButton {
text: "← " + I18n.tr("common.back")
outlined: true
visible: currentStep > 0 && !root.telemetryOnlyMode
Layout.preferredHeight: 44
onClicked: {
if (currentStep > 0) {
currentStep--;
}
}
}
NButton {
text: root.telemetryOnlyMode ? I18n.tr("setup.telemetry-wizard-done") : (currentStep === totalSteps - 1 ? I18n.tr("setup.all-done") : I18n.tr("common.continue") + " →")
Layout.preferredHeight: 44
onClicked: {
if (currentStep < totalSteps - 1) {
currentStep++;
} else {
panelContent.completeSetup();
}
}
}
}
}
}
}
}